From ca69001f420c9ed3c8a8b1687a27630526923e5b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 13:04:06 +0300 Subject: [PATCH 001/371] fix(deps): update dependency com.linecorp.armeria:armeria-bom to v1.32.5 (#1853) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 75b9ff2f8..6371cd782 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -138,7 +138,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.12.2")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.20.6")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) - implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.4")) + implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") From b29e9cbcc84714feff74522ec3b49e6dacd834e1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Apr 2025 12:49:21 -0700 Subject: [PATCH 002/371] fix(deps): update dependency io.grpc:grpc-netty-shaded to v1.72.0 (#1854) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- jmx-metrics/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index 9fd453d06..92195ec07 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -17,7 +17,7 @@ val groovyVersion = "3.0.24" dependencies { api(platform("org.codehaus.groovy:groovy-bom:$groovyVersion")) - implementation("io.grpc:grpc-netty-shaded:1.71.0") + implementation("io.grpc:grpc-netty-shaded:1.72.0") implementation("org.codehaus.groovy:groovy-jmx") implementation("org.codehaus.groovy:groovy") implementation("io.prometheus:simpleclient") From 30265cd40a76aae0c7253effe0e9a0de8b4f8f82 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 16 Apr 2025 14:22:38 -0700 Subject: [PATCH 003/371] Fix link exclusion (#1856) --- .github/workflows/reusable-markdown-link-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-markdown-link-check.yml b/.github/workflows/reusable-markdown-link-check.yml index e8692d6a3..e3addc927 100644 --- a/.github/workflows/reusable-markdown-link-check.yml +++ b/.github/workflows/reusable-markdown-link-check.yml @@ -17,6 +17,6 @@ jobs: # excluding links to pull requests and issues is done for performance args: > --include-fragments - --exclude "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issue|pull)/\\d+$" + --exclude "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$" --max-retries 6 . From 017bf8777c2e672a1b3de444ec8a67a7bb4ccb41 Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Thu, 17 Apr 2025 14:07:18 +0100 Subject: [PATCH 004/371] add jackshirazi as component owner for opamp-client (#1857) --- opamp-client/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/opamp-client/README.md b/opamp-client/README.md index c121aa904..9cb2dd97d 100644 --- a/opamp-client/README.md +++ b/opamp-client/README.md @@ -6,5 +6,6 @@ client [spec](https://github.com/open-telemetry/opamp-spec/blob/main/specificati ## Component owners - [Cesar Munoz](https://github.com/LikeTheSalad), Elastic +- [Jack Shirazi](https://github.com/jackshirazi), Elastic Learn more about component owners in [component_owners.yml](../.github.amrom.workers.devponent_owners.yml). From 806d5400454b76bb607749b7658d0a8e45d22f44 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 17 Apr 2025 13:25:28 -0700 Subject: [PATCH 005/371] fix(deps): update dependency net.ltgt.gradle:gradle-errorprone-plugin to v4.2.0 (#1858) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 406d7ff5b..80cadb3fb 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -13,7 +13,7 @@ repositories { dependencies { // When updating, update above in plugins too implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.3") - implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.1.0") + implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.2.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.1") } From fc199eb190a37c3b02998b7952079b314113b954 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:08:27 -0700 Subject: [PATCH 006/371] fix(deps): update errorprone packages to v2.38.0 (#1859) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lauri Tulmin --- .../src/main/kotlin/otel.errorprone-conventions.gradle.kts | 5 ++--- dependencyManagement/build.gradle.kts | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.errorprone-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.errorprone-conventions.gradle.kts index 1dfc48318..113576db3 100644 --- a/buildSrc/src/main/kotlin/otel.errorprone-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.errorprone-conventions.gradle.kts @@ -49,7 +49,6 @@ tasks { disable("UnnecessarilyFullyQualified") // TODO (trask) use animal sniffer - disable("Java7ApiChecker") disable("Java8ApiChecker") disable("AndroidJdkLibsChecker") @@ -79,8 +78,8 @@ tasks { // cognitive load is dubious. disable("YodaCondition") - // We get this warning in modules that compile for old java versions - disable("StringConcatToTextBlock") + // Requires adding compile dependency to JSpecify + disable("AddNullMarkedToPackageInfo") if (name.contains("Jmh") || name.contains("Test")) { // Allow underscore in test-type method names diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 5739a81f9..da7834662 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -25,8 +25,8 @@ dependencies { api("com.google.auto.service:auto-service-annotations:1.1.1") api("com.google.auto.value:auto-value:1.11.0") api("com.google.auto.value:auto-value-annotations:1.11.0") - api("com.google.errorprone:error_prone_annotations:2.37.0") - api("com.google.errorprone:error_prone_core:2.37.0") + api("com.google.errorprone:error_prone_annotations:2.38.0") + api("com.google.errorprone:error_prone_core:2.38.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") api("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha") api("io.prometheus:simpleclient:0.16.0") From c253efa539f424bbb1f8325443adafcdcd4b4643 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Wed, 23 Apr 2025 14:33:44 -0400 Subject: [PATCH 007/371] GCP Auth: update header injection implementation (#1860) --- gcp-auth-extension/build.gradle.kts | 10 +- .../contrib/gcp/auth/ConfigurableOption.java | 17 ++++ ...thAutoConfigurationCustomizerProvider.java | 39 +++++--- ...toConfigurationCustomizerProviderTest.java | 98 ++++++++++++------- .../auth/GcpAuthExtensionEndToEndTest.java | 3 +- .../src/test/resources/fake_user_creds.json | 7 ++ .../src/test/resources/fakecreds.json | 13 --- 7 files changed, 122 insertions(+), 65 deletions(-) create mode 100644 gcp-auth-extension/src/test/resources/fake_user_creds.json delete mode 100644 gcp-auth-extension/src/test/resources/fakecreds.json diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 112113e66..7e1460f67 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -55,6 +55,9 @@ dependencies { tasks { test { useJUnitPlatform() + // Unset relevant environment variables to provide a clean state for the tests + environment("GOOGLE_CLOUD_PROJECT", "") + environment("GOOGLE_CLOUD_QUOTA_PROJECT", "") // exclude integration test exclude("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class") } @@ -103,7 +106,7 @@ tasks.register("copyAgent") { }) } -tasks.register("IntegrationTest") { +tasks.register("IntegrationTestUserCreds") { dependsOn(tasks.shadowJar) dependsOn(tasks.named("copyAgent")) @@ -111,7 +114,7 @@ tasks.register("IntegrationTest") { // include only the integration test file include("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class") - val fakeCredsFilePath = project.file("src/test/resources/fakecreds.json").absolutePath + val fakeCredsFilePath = project.file("src/test/resources/fake_user_creds.json").absolutePath environment("GOOGLE_CLOUD_QUOTA_PROJECT", "quota-project-id") environment("GOOGLE_APPLICATION_CREDENTIALS", fakeCredsFilePath) @@ -127,6 +130,7 @@ tasks.register("IntegrationTest") { "-Dotel.metrics.exporter=none", "-Dotel.logs.exporter=none", "-Dotel.exporter.otlp.protocol=http/protobuf", - "-Dmockserver.logLevel=off" + "-Dotel.javaagent.debug=false", + "-Dmockserver.logLevel=trace" ) } 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 1bf90e48f..7928f9ab4 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 @@ -7,6 +7,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import java.util.Locale; +import java.util.Optional; import java.util.function.Supplier; /** @@ -101,4 +102,20 @@ String getConfiguredValueWithFallback(Supplier fallback) { return fallback.get(); } } + + /** + * 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() { + try { + return Optional.of(this.getConfiguredValue()); + } catch (ConfigurationException e) { + return Optional.empty(); + } + } } 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 70e9bdd3b..053aeef7d 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 @@ -20,8 +20,11 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; -import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; /** * An AutoConfigurationCustomizerProvider for Google Cloud Platform (GCP) OpenTelemetry (OTLP) @@ -40,7 +43,7 @@ public class GcpAuthAutoConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider { - static final String QUOTA_USER_PROJECT_HEADER = "X-Goog-User-Project"; + static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project"; static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id"; /** @@ -95,20 +98,34 @@ private static SpanExporter addAuthorizationHeaders( } private static Map getRequiredHeaderMap(GoogleCredentials credentials) { - Map gcpHeaders = new HashMap<>(); + Map> gcpHeaders; try { - credentials.refreshIfExpired(); + // this also refreshes the credentials, if required + gcpHeaders = credentials.getRequestMetadata(); } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_ADC_REFRESH, e); } - gcpHeaders.put("Authorization", "Bearer " + credentials.getAccessToken().getTokenValue()); - String configuredQuotaProjectId = - ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueWithFallback( - credentials::getQuotaProjectId); - if (configuredQuotaProjectId != null && !configuredQuotaProjectId.isEmpty()) { - gcpHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId); + // flatten list + Map flattenedHeaders = + gcpHeaders.entrySet().stream() + .collect( + Collectors.toMap( + Map.Entry::getKey, + entry -> + entry.getValue().stream() + .filter(Objects::nonNull) // Filter nulls + .filter(s -> !s.isEmpty()) // Filter empty strings + .collect(Collectors.joining(",")))); + // 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(); + maybeConfiguredQuotaProjectId.ifPresent( + configuredQuotaProjectId -> + flattenedHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId)); } - return gcpHeaders; + return flattenedHeaders; } // Updates the current resource with the attributes required for ingesting OTLP data on GCP. diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 39f626e61..5cbee0890 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -35,6 +35,7 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.io.IOException; import java.time.Duration; import java.time.Instant; import java.util.AbstractMap.SimpleEntry; @@ -214,26 +215,33 @@ public void testCustomizerFailWithMissingResourceProject() { @ParameterizedTest @MethodSource("provideQuotaBehaviorTestCases") @SuppressWarnings("CannotMockMethod") - public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) { + public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws IOException { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); - // Configure mock credentials to return fake access token - Mockito.when(mockedGoogleCredentials.getAccessToken()) - .thenReturn(new AccessToken("fake", Date.from(Instant.now()))); - - // To prevent unncecessary stubbings, mock getQuotaProjectId only when necessary - if (testCase.getUserSpecifiedQuotaProjectId() == null - || testCase.getUserSpecifiedQuotaProjectId().isEmpty()) { - String quotaProjectFromCredential = - testCase.getIsQuotaProjectPresentInCredentials() ? DUMMY_GCP_QUOTA_PROJECT_ID : null; - Mockito.when(mockedGoogleCredentials.getQuotaProjectId()) - .thenReturn(quotaProjectFromCredential); + + // Prepare request metadata + AccessToken fakeAccessToken = new AccessToken("fake", Date.from(Instant.now())); + ImmutableMap> mockedRequestMetadata; + if (testCase.getIsQuotaProjectPresentInMetadata()) { + mockedRequestMetadata = + ImmutableMap.of( + "Authorization", + Collections.singletonList("Bearer " + fakeAccessToken.getTokenValue()), + QUOTA_USER_PROJECT_HEADER, + Collections.singletonList(DUMMY_GCP_QUOTA_PROJECT_ID)); + } else { + mockedRequestMetadata = + ImmutableMap.of( + "Authorization", + Collections.singletonList("Bearer " + fakeAccessToken.getTokenValue())); } + // mock credentials to return the prepared request metadata + Mockito.when(mockedGoogleCredentials.getRequestMetadata()).thenReturn(mockedRequestMetadata); // configure environment according to test case String quotaProjectId = testCase.getUserSpecifiedQuotaProjectId(); // maybe empty string - if (testCase.getUserSpecifiedQuotaProjectId() != null) { + if (quotaProjectId != null) { // user specified a quota project id System.setProperty( ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getSystemProperty(), quotaProjectId); @@ -288,58 +296,62 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) { * indicates the expectation that the QUOTA_USER_PROJECT_HEADER should not be present in the * export headers. * - *

{@code true} for {@link QuotaProjectIdTestBehavior#getIsQuotaProjectPresentInCredentials()} + *

{@code true} for {@link QuotaProjectIdTestBehavior#getIsQuotaProjectPresentInMetadata()} * indicates that the mocked credentials are configured to provide DUMMY_GCP_QUOTA_PROJECT_ID as * the quota project ID. */ private static Stream provideQuotaBehaviorTestCases() { return Stream.of( + // If quota project present in metadata, it will be used Arguments.of( QuotaProjectIdTestBehavior.builder() .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID) - .setIsQuotaProjectPresentInCredentials(true) + .setIsQuotaProjectPresentInMetadata(true) .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) .build()), Arguments.of( QuotaProjectIdTestBehavior.builder() - .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID) - .setIsQuotaProjectPresentInCredentials(false) + .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id") + .setIsQuotaProjectPresentInMetadata(true) .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) .build()), + // If quota project not present in request metadata, then user specified project is used Arguments.of( QuotaProjectIdTestBehavior.builder() - .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id") - .setIsQuotaProjectPresentInCredentials(true) - .setExpectedQuotaProjectInHeader("my-custom-quota-project-id") + .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID) + .setIsQuotaProjectPresentInMetadata(false) + .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) .build()), Arguments.of( QuotaProjectIdTestBehavior.builder() .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id") - .setIsQuotaProjectPresentInCredentials(false) + .setIsQuotaProjectPresentInMetadata(false) .setExpectedQuotaProjectInHeader("my-custom-quota-project-id") .build()), + // Testing for special edge case inputs + // user-specified quota project is empty Arguments.of( QuotaProjectIdTestBehavior.builder() .setUserSpecifiedQuotaProjectId("") // user explicitly specifies empty - .setIsQuotaProjectPresentInCredentials(true) + .setIsQuotaProjectPresentInMetadata(true) .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) .build()), Arguments.of( QuotaProjectIdTestBehavior.builder() - .setUserSpecifiedQuotaProjectId(null) // user omits specifying quota project - .setIsQuotaProjectPresentInCredentials(true) - .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) + .setUserSpecifiedQuotaProjectId("") + .setIsQuotaProjectPresentInMetadata(false) + .setExpectedQuotaProjectInHeader(null) .build()), Arguments.of( QuotaProjectIdTestBehavior.builder() - .setUserSpecifiedQuotaProjectId("") - .setIsQuotaProjectPresentInCredentials(false) - .setExpectedQuotaProjectInHeader(null) + .setUserSpecifiedQuotaProjectId(null) // user omits specifying quota project + .setIsQuotaProjectPresentInMetadata(true) + .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID) .build()), Arguments.of( QuotaProjectIdTestBehavior.builder() .setUserSpecifiedQuotaProjectId(null) - .setIsQuotaProjectPresentInCredentials(false) + .setIsQuotaProjectPresentInMetadata(false) .setExpectedQuotaProjectInHeader(null) .build())); } @@ -367,7 +379,7 @@ abstract static class QuotaProjectIdTestBehavior { @Nullable abstract String getUserSpecifiedQuotaProjectId(); - abstract boolean getIsQuotaProjectPresentInCredentials(); + abstract boolean getIsQuotaProjectPresentInMetadata(); // If expected quota project in header is null, the header entry should not be present in export @Nullable @@ -382,9 +394,15 @@ static Builder builder() { abstract static class Builder { abstract Builder setUserSpecifiedQuotaProjectId(String quotaProjectId); - abstract Builder setIsQuotaProjectPresentInCredentials( - boolean quotaProjectPresentInCredentials); + abstract Builder setIsQuotaProjectPresentInMetadata(boolean quotaProjectPresentInMetadata); + /** + * Sets the expected quota project header value for the test case. A null value is allowed, + * and it indicates that the header should not be present in the export request. + * + * @param expectedQuotaProjectInHeader the expected header value to match in the export + * headers. + */ abstract Builder setExpectedQuotaProjectInHeader(String expectedQuotaProjectInHeader); abstract QuotaProjectIdTestBehavior build(); @@ -393,10 +411,18 @@ abstract Builder setIsQuotaProjectPresentInCredentials( @SuppressWarnings("CannotMockMethod") private void prepareMockBehaviorForGoogleCredentials() { - Mockito.when(mockedGoogleCredentials.getQuotaProjectId()) - .thenReturn(DUMMY_GCP_QUOTA_PROJECT_ID); - Mockito.when(mockedGoogleCredentials.getAccessToken()) - .thenReturn(new AccessToken("fake", Date.from(Instant.now()))); + AccessToken fakeAccessToken = new AccessToken("fake", Date.from(Instant.now())); + try { + Mockito.when(mockedGoogleCredentials.getRequestMetadata()) + .thenReturn( + ImmutableMap.of( + "Authorization", + Collections.singletonList("Bearer " + fakeAccessToken.getTokenValue()), + QUOTA_USER_PROJECT_HEADER, + Collections.singletonList(DUMMY_GCP_QUOTA_PROJECT_ID))); + } catch (IOException e) { + throw new RuntimeException(e); + } } private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) { diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java index 421d4fcec..e04baed93 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java @@ -89,10 +89,9 @@ public static void setup() throws NoSuchAlgorithmException, KeyManagementExcepti // Set up mock OTLP backend server to which traces will be exported backendServer = ClientAndServer.startClientAndServer(EXPORTER_ENDPOINT_PORT); backendServer.when(request()).respond(response().withStatusCode(200)); - - // Set up the mock gcp metadata server to provide fake credentials String accessTokenResponse = "{\"access_token\": \"fake.access_token\",\"expires_in\": 3600, \"token_type\": \"Bearer\"}"; + mockGcpOAuth2Server = ClientAndServer.startClientAndServer(MOCK_GCP_OAUTH2_PORT); MockServerClient mockServerClient = diff --git a/gcp-auth-extension/src/test/resources/fake_user_creds.json b/gcp-auth-extension/src/test/resources/fake_user_creds.json new file mode 100644 index 000000000..fd798897f --- /dev/null +++ b/gcp-auth-extension/src/test/resources/fake_user_creds.json @@ -0,0 +1,7 @@ +{ + "client_id": "....apps.googleusercontent.com", + "client_secret": "...", + "refresh_token": "1//...", + "quota_project_id": "your-configured-quota-project", + "type": "authorized_user" +} diff --git a/gcp-auth-extension/src/test/resources/fakecreds.json b/gcp-auth-extension/src/test/resources/fakecreds.json deleted file mode 100644 index 1000f70db..000000000 --- a/gcp-auth-extension/src/test/resources/fakecreds.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "type": "service_account", - "project_id": "quota-project-id", - "private_key_id": "aljmafmlamlmmasma", - "private_key": "-----BEGIN PRIVATE KEY-----\nMIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALX0PQoe1igW12ikv1bN/r9lN749y2ijmbc/mFHPyS3hNTyOCjDvBbXYbDhQJzWVUikh4mvGBA07qTj79Xc3yBDfKP2IeyYQIFe0t0zkd7R9Zdn98Y2rIQC47aAbDfubtkU1U72t4zL11kHvoa0/RuFZjncvlr42X7be7lYh4p3NAgMBAAECgYASk5wDw4Az2ZkmeuN6Fk/y9H+Lcb2pskJIXjrL533vrDWGOC48LrsThMQPv8cxBky8HFSEklPpkfTF95tpD43iVwJRB/GrCtGTw65IfJ4/tI09h6zGc4yqvIo1cHX/LQ+SxKLGyir/dQM925rGt/VojxY5ryJR7GLbCzxPnJm/oQJBANwOCO6D2hy1LQYJhXh7O+RLtA/tSnT1xyMQsGT+uUCMiKS2bSKx2wxo9k7h3OegNJIu1q6nZ6AbxDK8H3+d0dUCQQDTrPSXagBxzp8PecbaCHjzNRSQE2in81qYnrAFNB4o3DpHyMMY6s5ALLeHKscEWnqP8Ur6X4PvzZecCWU9BKAZAkAutLPknAuxSCsUOvUfS1i87ex77Ot+w6POp34pEX+UWb+u5iFn2cQacDTHLV1LtE80L8jVLSbrbrlH43H0DjU5AkEAgidhycxS86dxpEljnOMCw8CKoUBd5I880IUahEiUltk7OLJYS/Ts1wbn3kPOVX3wyJs8WBDtBkFrDHW2ezth2QJADj3e1YhMVdjJW5jqwlD/VNddGjgzyunmiZg0uOXsHXbytYmsA545S8KRQFaJKFXYYFo2kOjqOiC1T2cAzMDjCQ==\n-----END PRIVATE KEY-----\n", - "client_email": "sample@appspot.gserviceaccount.com", - "client_id": "100000000000000000221", - "auth_uri": "https://accounts.google.com/o/oauth2/auth", - "token_uri": "https://oauth2.googleapis.com/token", - "auth_provider_x509_cert_url": "", - "client_x509_cert_url": "", - "universe_domain": "googleapis.com" -} From 9076a8bfc4f38a1b5e6b51a586359d4d8b85a7aa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 10:04:46 +0300 Subject: [PATCH 008/371] fix(deps): update testcontainers-java monorepo to v1.21.0 (#1862) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- dependencyManagement/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 6371cd782..a337dff27 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -136,7 +136,7 @@ testing { implementation(project(project.path)) implementation(enforcedPlatform("org.junit:junit-bom:5.12.2")) - implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.20.6")) + implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.0")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index da7834662..0b812c90c 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { api("org.junit-pioneer:junit-pioneer:1.9.1") api("org.skyscreamer:jsonassert:1.5.3") api("org.apache.kafka:kafka-clients:3.9.0") - api("org.testcontainers:kafka:1.20.6") + api("org.testcontainers:kafka:1.21.0") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") api("tools.profiler:async-profiler:3.0") From a096565d4e2aa26f6199711f18a49c0634ccc2eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Apr 2025 18:07:15 +0300 Subject: [PATCH 009/371] fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.4.5 (#1864) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index 79d2dbb80..d277ab59e 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.4 + 3.4.5 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index 1bd86fce0..c5cac1512 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.4 + 3.4.5 io.opentelemetry.contrib.maven.test From 451f9bf95bfd659d566d9c902918095e34d8d91f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 09:53:01 +0300 Subject: [PATCH 010/371] fix(deps): update dependency com.fasterxml.jackson:jackson-bom to v2.19.0 (#1865) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 0b812c90c..55f3e3288 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { // (the constraints section below doesn't have this issue, and will only show up // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) - api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.18.3")) + api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.0")) constraints { api("io.opentelemetry.semconv:opentelemetry-semconv:${semconvVersion}") From aaf822c38ae3d72732cf4ab9e2ed0e954169be7c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 10:27:18 -0700 Subject: [PATCH 011/371] chore(deps): update dependency gradle to v8.14 (#1866) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.jar | Bin 43705 -> 43764 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 4 ++-- gradlew.bat | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 9bbc975c742b298b441bfb90dbc124400a3751b9..1b33c55baabb587c669f562ae36f953de2481846 100644 GIT binary patch delta 642 zcmdmamFde>rVZJA^}0Q$xegf!xPEW^+5YDM%iT2bEgct9o+jH~+sJas#HZ=szO|** z=Pj=X_vx?W&DSwKck|WWn~hffsvnQ+42*W$b7b0$SCcOoZ`{W{^$^pk;4>8-A*-)$ z?n(Po`1$6Jn_u?t-L+tsPyZ2#X}8T6OS8pAU;kdgd+_Hw4z4TW0p9E!T+=f7-c&O% zFic^X{7^$?^Ho04eona9n#mGMxKhA=~8B%JN`M zMhm5wc-2v)$``sY$!Q`9xiU@DhI73ZxiGEKg>yIPs)NmWwMdF-ngLXpZSqV5ez36n zVkxF2rjrjWR+_xr6e6@_u@s~2uv{9vi*1pj2)BjFD+-%@&pRVP1f{O1glxTOp2-62Ph;v z`N1+vCd)9ea)af*Ol1*JCfnp$%Uu}%OuoN7g2}3C@`L5FlP#(sA=|h@iixuZC?qp^ z=L$=v$ZoI}|87Wh=&h7udff{aieKr*l+zDp?pf)_bbRvUf>kn;HCDMXNlgbbo!QRK I1x7am0No)LiU0rr delta 584 zcmexzm1*ZyrVZJAexH5Moc8h7)w{^+t*dqJ%=yhh23L$9JpFV=_k`zJ-?Q4DI*eSe z+ES)HSrVnWLtJ&)lO%hRkV9zl5qqWRt0e;bb zPPo`)y?HTAyZI&u&X<|2$FDHCf4;!v8}p=?Tm`^F0`u(|1ttf~&t$qP3KUSD>@TJQ zRwJ}Pim6NzEc8KA6)e;S6gs8=7IIL8sQL*MYEuRYO;Uj<%3UbMbV&^&!Zvx+LKmjT z8Zch6rYP7Tw?$Hn(UTJwWiS=$f{lB(C=e*%usDV})0AQIK~sat=ND@+Gg*Pyij!rR z*fa02W|%BsV++>4W{DKDGSIUEHd2$P+8ct!RF+CHDowUuTEZOZ%rJSQv*qOXOSPDN zT|sP-$p*_3ncsWB*qoD7JQcyZ9xan%cJP6Tb4-?AZpr*F6v98hoNaPJm@HV`yya5N z))6pqFXn@}P(3T0nEzM8*c_9KtE9o|_pFd&K35GBXP^9Kg(b6GH-z8S4GDzIl~T+b zdLd#meKKHu$5u))8cu$=GKINkGDPOUD)!0$C(BH(U!}!-e;Q0ok8Sc?V1zRO04>ts AA^-pY diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 36e4933e1..247cf2a9f 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=20f1b1176237254a6fc204d8434196fa11a4cfb387567519c61556e8710aed78 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip +distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index faf93008b..23d15a936 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,7 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar +CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ -classpath "$CLASSPATH" \ - org.gradle.wrapper.GradleWrapperMain \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" # Stop when "xargs" is not available. diff --git a/gradlew.bat b/gradlew.bat index 9d21a2183..db3a6ac20 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,11 @@ goto fail :execute @rem Setup the command line -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar +set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From 32c6de1c846a25b367a0971165324b9c6adf6453 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Apr 2025 11:03:15 -0700 Subject: [PATCH 012/371] chore(deps): update plugin com.gradle.develocity to v4.0.1 (#1867) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index ef819d82a..ab00e263f 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.github.johnrengelman.shadow") version "8.1.1" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.0" + id("com.gradle.develocity") version "4.0.1" } } From 5971fcc6f796dd48603bc120ebbe0177ac747863 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Apr 2025 19:08:46 -0700 Subject: [PATCH 013/371] chore(deps): update plugin ru.vyarus.animalsniffer to v2.0.1 (#1869) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- disk-buffering/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index edc8e862c..92a8f4e3a 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("otel.publish-conventions") id("com.github.johnrengelman.shadow") id("me.champeau.jmh") version "0.7.3" - id("ru.vyarus.animalsniffer") version "2.0.0" + id("ru.vyarus.animalsniffer") version "2.0.1" id("com.squareup.wire") version "5.3.1" } From e437944860e939f2d00c070c73587b23ac56d1c5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Apr 2025 13:56:32 +0300 Subject: [PATCH 014/371] fix(deps): update dependency com.uber.nullaway:nullaway to v0.12.7 (#1870) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 55f3e3288..7511ffddf 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") api("com.squareup.okhttp3:okhttp:4.12.0") - api("com.uber.nullaway:nullaway:0.12.6") + api("com.uber.nullaway:nullaway:0.12.7") api("org.assertj:assertj-core:3.27.3") api("org.awaitility:awaitility:4.3.0") api("org.bouncycastle:bcpkix-jdk15on:1.70") From 3fc4309a4a97964e453859ac936c8e693f19fd55 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 2 May 2025 08:25:54 +0300 Subject: [PATCH 015/371] fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.34.0 (#1871) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 7e1460f67..6a0fbd7c2 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.33.1") + implementation("com.google.auth:google-auth-library-oauth2-http:1.34.0") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From d8c3f95e1409857de23c8dd47c077db2132ee2d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 May 2025 19:01:28 -0700 Subject: [PATCH 016/371] fix(deps): update dependency com.github.luben:zstd-jni to v1.5.7-3 (#1873) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- compressors/compressor-zstd/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compressors/compressor-zstd/build.gradle.kts b/compressors/compressor-zstd/build.gradle.kts index 63db3ed20..7d52fb224 100644 --- a/compressors/compressor-zstd/build.gradle.kts +++ b/compressors/compressor-zstd/build.gradle.kts @@ -9,7 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.compressor.zstd") dependencies { api("io.opentelemetry:opentelemetry-exporter-common") - implementation("com.github.luben:zstd-jni:1.5.7-2") + implementation("com.github.luben:zstd-jni:1.5.7-3") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") From 4ae092cf6acc622a0155289d284989565fc23d3a Mon Sep 17 00:00:00 2001 From: Jonas Kunz Date: Tue, 6 May 2025 04:02:37 +0200 Subject: [PATCH 017/371] Upgrade async-profiler to 4.0 in inferred spans feature (#1872) --- dependencyManagement/build.gradle.kts | 2 +- inferred-spans/build.gradle.kts | 4 ++++ .../contrib/inferredspans/internal/SamplingProfiler.java | 2 +- .../inferredspans/internal/SamplingProfilerTest.java | 6 +++--- inferred-spans/src/test/resources/logging.properties | 4 ++++ 5 files changed, 13 insertions(+), 5 deletions(-) create mode 100644 inferred-spans/src/test/resources/logging.properties diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7511ffddf..d8cc0188b 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -54,7 +54,7 @@ dependencies { api("org.testcontainers:kafka:1.21.0") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") - api("tools.profiler:async-profiler:3.0") + api("tools.profiler:async-profiler:4.0") api("com.blogspot.mydailyjava:weak-lock-free:0.18") api("org.agrona:agrona:1.22.0") } diff --git a/inferred-spans/build.gradle.kts b/inferred-spans/build.gradle.kts index dba8e0334..d0921a940 100644 --- a/inferred-spans/build.gradle.kts +++ b/inferred-spans/build.gradle.kts @@ -42,4 +42,8 @@ tasks { } } } + + withType().configureEach { + jvmArgs("-Djava.util.logging.config.file=${project.projectDir.resolve("src/test/resources/logging.properties")}") + } } diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java index d05496084..23e0dfc99 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java @@ -426,7 +426,7 @@ private void profile(Duration profilingDuration) throws Exception { String createStartCommand() { StringBuilder startCommand = - new StringBuilder("start,jfr,clock=m,event=wall,cstack=n,interval=") + new StringBuilder("start,jfr,clock=m,event=wall,nobatch,cstack=n,interval=") .append(config.getSamplingInterval().toMillis()) .append("ms,filter,file=") .append(jfrFile) diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerTest.java index a355cb76f..b97ce8729 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerTest.java @@ -144,13 +144,13 @@ void testStartCommand() { setupProfiler(false); assertThat(setup.profiler.createStartCommand()) .isEqualTo( - "start,jfr,clock=m,event=wall,cstack=n,interval=5ms,filter,file=null,safemode=0"); + "start,jfr,clock=m,event=wall,nobatch,cstack=n,interval=5ms,filter,file=null,safemode=0"); setup.close(); setupProfiler(config -> config.startScheduledProfiling(false).profilerLoggingEnabled(false)); assertThat(setup.profiler.createStartCommand()) .isEqualTo( - "start,jfr,clock=m,event=wall,cstack=n,interval=5ms,filter,file=null,safemode=0,loglevel=none"); + "start,jfr,clock=m,event=wall,nobatch,cstack=n,interval=5ms,filter,file=null,safemode=0,loglevel=none"); setup.close(); setupProfiler( @@ -162,7 +162,7 @@ void testStartCommand() { .asyncProfilerSafeMode(14)); assertThat(setup.profiler.createStartCommand()) .isEqualTo( - "start,jfr,clock=m,event=wall,cstack=n,interval=10ms,filter,file=null,safemode=14,loglevel=none"); + "start,jfr,clock=m,event=wall,nobatch,cstack=n,interval=10ms,filter,file=null,safemode=14,loglevel=none"); } @Test diff --git a/inferred-spans/src/test/resources/logging.properties b/inferred-spans/src/test/resources/logging.properties new file mode 100644 index 000000000..f9ad0c6c2 --- /dev/null +++ b/inferred-spans/src/test/resources/logging.properties @@ -0,0 +1,4 @@ +handlers=java.util.logging.ConsoleHandler +.level=ALL +java.util.logging.ConsoleHandler.level=ALL +java.util.logging.ConsoleHandler.formatter=java.util.logging.SimpleFormatter From e047c1ae629360f1f0b18dfce5f0707526656ae6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 6 May 2025 13:24:41 -0700 Subject: [PATCH 018/371] chore(deps): update weekly update (#1876) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/backport.yml | 2 +- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/prepare-patch-release.yml | 2 +- .github/workflows/prepare-release-branch.yml | 4 ++-- .github/workflows/release.yml | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index e34c77489..aa0f5d250 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -29,7 +29,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ff49bce2f..a0c462705 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 - name: Initialize CodeQL - uses: github/codeql-action/init@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 + uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: languages: java, actions # using "latest" helps to keep up with the latest Kotlin support @@ -52,4 +52,4 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 + uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 886c27e3e..c969bba72 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15 + uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 with: sarif_file: results.sarif diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml index 050fc5a7f..e4361dea6 100644 --- a/.github/workflows/prepare-patch-release.yml +++ b/.github/workflows/prepare-patch-release.yml @@ -47,7 +47,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/prepare-release-branch.yml b/.github/workflows/prepare-release-branch.yml index 1e2a00c60..d0db88cd0 100644 --- a/.github/workflows/prepare-release-branch.yml +++ b/.github/workflows/prepare-release-branch.yml @@ -59,7 +59,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} @@ -116,7 +116,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 244b009d5..f75f133db 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -221,7 +221,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@3ff1caaa28b64c9cc276ce0a02e2ff584f3900c5 # v2.0.2 + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} From 22daafd325db8fbbde7f4093136b43a164e84a84 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 08:03:22 +0300 Subject: [PATCH 019/371] chore(deps): update lycheeverse/lychee-action action to v2.4.1 (#1878) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-markdown-link-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-markdown-link-check.yml b/.github/workflows/reusable-markdown-link-check.yml index e3addc927..b204aa3ce 100644 --- a/.github/workflows/reusable-markdown-link-check.yml +++ b/.github/workflows/reusable-markdown-link-check.yml @@ -12,7 +12,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: lycheeverse/lychee-action@1d97d84f0bc547f7b25f4c2170d87d810dc2fb2c # v2.4.0 + - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 with: # excluding links to pull requests and issues is done for performance args: > From fc9d836f0bc0690e3c158dd1c4ad83f743ef6623 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 May 2025 16:47:15 +0300 Subject: [PATCH 020/371] fix(deps): update micrometer to v1.14.7 (#1879) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- micrometer-meter-provider/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index 99b3fb01b..aeadaae2b 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.14.6") + testImplementation("io.micrometer:micrometer-core:1.14.7") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.14.6") + implementation("io.micrometer:micrometer-registry-prometheus:1.14.7") } } } From 9b8178d1ce2cdb0de6f10d951f297a457040d82f Mon Sep 17 00:00:00 2001 From: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Date: Mon, 12 May 2025 14:58:12 -0700 Subject: [PATCH 021/371] Update Renovate configuration (#1880) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/renovate.json5 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 0efdf7d3a..845d916a8 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -1,9 +1,8 @@ { $schema: 'https://docs.renovatebot.com/renovate-schema.json', extends: [ - 'config:recommended', - 'docker:pinDigests', - 'helpers:pinGitHubActionDigests', + 'config:best-practices', + 'helpers:pinGitHubActionDigestsToSemver', ], ignorePresets: [ ':ignoreModulesAndTests', // needed to keep maven-extension test pom files up-to-date From 4e8db837c18298ca910c6b7a94055943a3902818 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 01:54:23 +0000 Subject: [PATCH 022/371] chore(config): migrate renovate config (#1881) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker --- .github/renovate.json5 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 845d916a8..9425fd488 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -164,8 +164,8 @@ { customType: 'regex', datasourceTemplate: 'npm', - fileMatch: [ - '^.github/workflows/', + managerFilePatterns: [ + '/^.github/workflows//', ], matchStrings: [ 'npx (?[^@]+)@(?[^\\s]+)', From bd739679ea5eacb93c2cb683f73b29c920d4027d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 13 May 2025 10:37:30 +0300 Subject: [PATCH 023/371] fix(deps): update micrometer to v1.15.0 (#1883) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- micrometer-meter-provider/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index aeadaae2b..eed860e4b 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.14.7") + testImplementation("io.micrometer:micrometer-core:1.15.0") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.14.7") + implementation("io.micrometer:micrometer-registry-prometheus:1.15.0") } } } From 7445910478e3e045695139c75f703ef31801c965 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 13 May 2025 20:45:17 -0400 Subject: [PATCH 024/371] Update cassandra link (#1885) --- jmx-metrics/docs/target-systems/cassandra.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-metrics/docs/target-systems/cassandra.md b/jmx-metrics/docs/target-systems/cassandra.md index 50a491fab..2edcd9e64 100644 --- a/jmx-metrics/docs/target-systems/cassandra.md +++ b/jmx-metrics/docs/target-systems/cassandra.md @@ -1,7 +1,7 @@ # Casandra Metrics The JMX Metric Gatherer provides built in Cassandra metric gathering capabilities. -These metrics are sourced from Cassandra's exposed Dropwizard Metrics for each node: https://cassandra.apache.org/doc/latest/operating/metrics.html. +These metrics are sourced from Cassandra's exposed Dropwizard Metrics for each node: https://cassandra.apache.org/doc/latest/cassandra/managing/operating/metrics.html. ## Metrics From b897a0ec76c91e3a699187c29448fcc9c3faafa8 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Tue, 13 May 2025 19:11:00 -0700 Subject: [PATCH 025/371] Lychee / markdown link check - set max concurrency (#1884) --- .github/workflows/reusable-markdown-link-check.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reusable-markdown-link-check.yml b/.github/workflows/reusable-markdown-link-check.yml index b204aa3ce..3120678ce 100644 --- a/.github/workflows/reusable-markdown-link-check.yml +++ b/.github/workflows/reusable-markdown-link-check.yml @@ -19,4 +19,5 @@ jobs: --include-fragments --exclude "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$" --max-retries 6 + --max-concurrency 1 . From 34937d2e24bceb3a72cab71c02240a2756755a06 Mon Sep 17 00:00:00 2001 From: Jan Engehausen Date: Thu, 15 May 2025 16:37:48 +0200 Subject: [PATCH 026/371] add option to record transferred artifacts (#1875) Co-authored-by: Cyrille Le Clerc --- maven-extension/README.md | 17 +- maven-extension/build.gradle.kts | 4 +- .../maven/ChainedTransferListener.java | 70 ++++++++ .../maven/OpenTelemetrySdkService.java | 10 +- .../maven/OtelExecutionListener.java | 18 -- .../maven/OtelLifecycleParticipant.java | 51 ++++++ .../maven/OtelTransferListener.java | 166 ++++++++++++++++++ .../io/opentelemetry/maven/SpanRegistry.java | 38 ++++ .../maven/ToUpperCaseTextMapGetter.java | 28 +++ .../semconv/MavenOtelSemanticAttributes.java | 10 ++ 10 files changed, 383 insertions(+), 29 deletions(-) create mode 100644 maven-extension/src/main/java/io/opentelemetry/maven/ChainedTransferListener.java create mode 100644 maven-extension/src/main/java/io/opentelemetry/maven/OtelTransferListener.java create mode 100644 maven-extension/src/main/java/io/opentelemetry/maven/ToUpperCaseTextMapGetter.java diff --git a/maven-extension/README.md b/maven-extension/README.md index 3ae23c7f9..91ec71744 100644 --- a/maven-extension/README.md +++ b/maven-extension/README.md @@ -61,14 +61,15 @@ Without this setting, the traces won't be exported and the OpenTelemetry Maven E The Maven OpenTelemetry Extension supports a subset of the [OpenTelemetry autoconfiguration environment variables and JVM system properties](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure). -| System property
Environment variable | Default value | Description | -|--------------------------------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| `otel.traces.exporter`
`OTEL_TRACES_EXPORTER` | `none` | Select the OpenTelemetry exporter for tracing, the currently only supported values are `none` and `otlp`. `none` makes the instrumentation NoOp | -| `otel.exporter.otlp.endpoint`
`OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | The OTLP traces and metrics endpoint to connect to. Must be a URL with a scheme of either `http` or `https` based on the use of TLS. | -| `otel.exporter.otlp.headers`
`OTEL_EXPORTER_OTLP_HEADERS` | | Key-value pairs separated by commas to pass as request headers on OTLP trace and metrics requests. | -| `otel.exporter.otlp.timeout`
`OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. | -| `otel.resource.attributes`
`OTEL_RESOURCE_ATTRIBUTES` | | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 | -| `otel.instrumentation.maven.mojo.enabled`
`OTEL_INSTRUMENTATION_MAVEN_MOJO_ENABLED` | `true` | Whether to create spans for mojo goal executions, `true` or `false`. Can be configured to reduce the number of spans created for large builds. | +| System property
Environment variable | Default value | Description | +|----------------------------------------------------------------------------------------------------|-------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| `otel.traces.exporter`
`OTEL_TRACES_EXPORTER` | `none` | Select the OpenTelemetry exporter for tracing, the currently only supported values are `none` and `otlp`. `none` makes the instrumentation NoOp | +| `otel.exporter.otlp.endpoint`
`OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | The OTLP traces and metrics endpoint to connect to. Must be a URL with a scheme of either `http` or `https` based on the use of TLS. | +| `otel.exporter.otlp.headers`
`OTEL_EXPORTER_OTLP_HEADERS` | | Key-value pairs separated by commas to pass as request headers on OTLP trace and metrics requests. | +| `otel.exporter.otlp.timeout`
`OTEL_EXPORTER_OTLP_TIMEOUT` | `10000` | The maximum waiting time, in milliseconds, allowed to send each OTLP trace and metric batch. | +| `otel.resource.attributes`
`OTEL_RESOURCE_ATTRIBUTES` | | Specify resource attributes in the following format: key1=val1,key2=val2,key3=val3 | +| `otel.instrumentation.maven.mojo.enabled`
`OTEL_INSTRUMENTATION_MAVEN_MOJO_ENABLED` | `true` | Whether to create spans for mojo goal executions, `true` or `false`. Can be configured to reduce the number of spans created for large builds. | +| `otel.instrumentation.maven.transfer.enabled`
`OTEL_INSTRUMENTATION_MAVEN_TRANSFER_ENABLED` | `false` | Whether to create spans for artifact transfers, `true` or `false`. Can be activated to understand impact of artifact transfers on performances. | ℹ️ The `service.name` is set to `maven` and the `service.version` to the version of the Maven runtime in use. diff --git a/maven-extension/build.gradle.kts b/maven-extension/build.gradle.kts index 4304534f8..d14faa992 100644 --- a/maven-extension/build.gradle.kts +++ b/maven-extension/build.gradle.kts @@ -6,7 +6,7 @@ plugins { } // NOTE -// `META-INF/sis/javax.inject.Named` is manually handled under src/main/resources because there is +// `META-INF/sisu/javax.inject.Named` is manually handled under src/main/resources because there is // no Gradle equivalent to the Maven plugin `org.eclipse.sisu:sisu-maven-plugin` description = "Maven extension to observe Maven builds with distributed traces using OpenTelemetry SDK" @@ -24,7 +24,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") implementation("io.opentelemetry:opentelemetry-exporter-otlp") implementation("io.opentelemetry.semconv:opentelemetry-semconv") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/ChainedTransferListener.java b/maven-extension/src/main/java/io/opentelemetry/maven/ChainedTransferListener.java new file mode 100644 index 000000000..692d1aff3 --- /dev/null +++ b/maven-extension/src/main/java/io/opentelemetry/maven/ChainedTransferListener.java @@ -0,0 +1,70 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; +import org.eclipse.aether.transfer.TransferCancelledException; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferListener; + +/** + * Util class to chain multiple {@link TransferListener} as Maven APIs don't offer this capability. + */ +final class ChainedTransferListener implements TransferListener { + + private final List listeners; + + /** + * @param listeners {@code null} values are filtered + */ + ChainedTransferListener(TransferListener... listeners) { + this.listeners = Arrays.stream(listeners).filter(e -> e != null).collect(Collectors.toList()); + } + + @Override + public void transferInitiated(TransferEvent event) throws TransferCancelledException { + for (TransferListener listener : this.listeners) { + listener.transferInitiated(event); + } + } + + @Override + public void transferStarted(TransferEvent event) throws TransferCancelledException { + for (TransferListener listener : this.listeners) { + listener.transferStarted(event); + } + } + + @Override + public void transferProgressed(TransferEvent event) throws TransferCancelledException { + for (TransferListener listener : this.listeners) { + listener.transferProgressed(event); + } + } + + @Override + public void transferCorrupted(TransferEvent event) throws TransferCancelledException { + for (TransferListener listener : this.listeners) { + listener.transferCorrupted(event); + } + } + + @Override + public void transferSucceeded(TransferEvent event) { + for (TransferListener listener : this.listeners) { + listener.transferSucceeded(event); + } + } + + @Override + public void transferFailed(TransferEvent event) { + for (TransferListener listener : this.listeners) { + listener.transferFailed(event); + } + } +} diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java b/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java index a357b3177..0d2e5150e 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/OpenTelemetrySdkService.java @@ -49,10 +49,12 @@ public final class OpenTelemetrySdkService implements Closeable { private final boolean mojosInstrumentationEnabled; + private final boolean transferInstrumentationEnabled; + private boolean disposed; public OpenTelemetrySdkService() { - logger.debug( + logger.info( "OpenTelemetry: Initialize OpenTelemetrySdkService v{}...", MavenOtelSemanticAttributes.TELEMETRY_DISTRO_VERSION_VALUE); @@ -76,6 +78,8 @@ public OpenTelemetrySdkService() { this.mojosInstrumentationEnabled = configProperties.getBoolean("otel.instrumentation.maven.mojo.enabled", true); + this.transferInstrumentationEnabled = + configProperties.getBoolean("otel.instrumentation.maven.transfer.enabled", false); this.tracer = openTelemetrySdk.getTracer("io.opentelemetry.contrib.maven", VERSION); } @@ -154,4 +158,8 @@ public ContextPropagators getPropagators() { public boolean isMojosInstrumentationEnabled() { return mojosInstrumentationEnabled; } + + public boolean isTransferInstrumentationEnabled() { + return transferInstrumentationEnabled; + } } diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/OtelExecutionListener.java b/maven-extension/src/main/java/io/opentelemetry/maven/OtelExecutionListener.java index 8da5c3acd..830412f7f 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/OtelExecutionListener.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/OtelExecutionListener.java @@ -17,12 +17,9 @@ import io.opentelemetry.maven.handler.MojoGoalExecutionHandler; import io.opentelemetry.maven.handler.MojoGoalExecutionHandlerConfiguration; import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes; -import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; import org.apache.maven.execution.AbstractExecutionListener; import org.apache.maven.execution.ExecutionEvent; import org.apache.maven.execution.ExecutionListener; @@ -338,19 +335,4 @@ public void sessionEnded(ExecutionEvent event) { logger.debug("OpenTelemetry: Maven session ended, end root span"); spanRegistry.removeRootSpan().end(); } - - private static class ToUpperCaseTextMapGetter implements TextMapGetter> { - @Override - public Iterable keys(Map environmentVariables) { - return environmentVariables.keySet(); - } - - @Override - @Nullable - public String get(@Nullable Map environmentVariables, @Nonnull String key) { - return environmentVariables == null - ? null - : environmentVariables.get(key.toUpperCase(Locale.ROOT)); - } - } } diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/OtelLifecycleParticipant.java b/maven-extension/src/main/java/io/opentelemetry/maven/OtelLifecycleParticipant.java index d613f9607..692a70650 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/OtelLifecycleParticipant.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/OtelLifecycleParticipant.java @@ -11,6 +11,9 @@ import org.apache.maven.AbstractMavenLifecycleParticipant; import org.apache.maven.execution.ExecutionListener; import org.apache.maven.execution.MavenSession; +import org.eclipse.aether.DefaultRepositorySystemSession; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.transfer.TransferListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -25,6 +28,8 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic private final OtelExecutionListener otelExecutionListener; + private final OtelTransferListener otelTransferListener; + /** * Manually instantiate {@link OtelExecutionListener} and hook it in the Maven build lifecycle * because Maven Sisu doesn't load it when Maven Plexus did. @@ -34,6 +39,14 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic OpenTelemetrySdkService openTelemetrySdkService, SpanRegistry spanRegistry) { this.openTelemetrySdkService = openTelemetrySdkService; this.otelExecutionListener = new OtelExecutionListener(spanRegistry, openTelemetrySdkService); + this.otelTransferListener = new OtelTransferListener(spanRegistry, openTelemetrySdkService); + } + + @Override + public void afterSessionStart(MavenSession session) { + if (openTelemetrySdkService.isTransferInstrumentationEnabled()) { + registerTransferListener(session); + } } /** @@ -43,6 +56,10 @@ public final class OtelLifecycleParticipant extends AbstractMavenLifecyclePartic */ @Override public void afterProjectsRead(MavenSession session) { + registerExecutionListener(session); + } + + void registerExecutionListener(MavenSession session) { ExecutionListener initialExecutionListener = session.getRequest().getExecutionListener(); if (initialExecutionListener instanceof ChainedExecutionListener || initialExecutionListener instanceof OtelExecutionListener) { @@ -64,6 +81,40 @@ public void afterProjectsRead(MavenSession session) { } } + void registerTransferListener(MavenSession session) { + RepositorySystemSession repositorySession = session.getRepositorySession(); + TransferListener initialTransferListener = repositorySession.getTransferListener(); + if (initialTransferListener instanceof ChainedTransferListener + || initialTransferListener instanceof OtelTransferListener) { + // already initialized + logger.debug( + "OpenTelemetry: OpenTelemetry extension already registered as transfer listener, skip."); + } else if (initialTransferListener == null) { + setTransferListener(this.otelTransferListener, repositorySession, session); + logger.debug( + "OpenTelemetry: OpenTelemetry extension registered as transfer listener. No transfer listener initially defined"); + } else { + setTransferListener( + new ChainedTransferListener(this.otelTransferListener, initialTransferListener), + repositorySession, + session); + logger.debug( + "OpenTelemetry: OpenTelemetry extension registered as transfer listener. InitialTransferListener: {}", + initialTransferListener); + } + } + + void setTransferListener( + TransferListener transferListener, + RepositorySystemSession repositorySession, + MavenSession session) { + if (repositorySession instanceof DefaultRepositorySystemSession) { + ((DefaultRepositorySystemSession) repositorySession).setTransferListener(transferListener); + } else { + logger.warn("OpenTelemetry: Cannot set transfer listener"); + } + } + @Override public void afterSessionEnd(MavenSession session) { // Workaround https://issues.apache.org/jira/browse/MNG-8217 diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/OtelTransferListener.java b/maven-extension/src/main/java/io/opentelemetry/maven/OtelTransferListener.java new file mode 100644 index 000000000..53c2aa729 --- /dev/null +++ b/maven-extension/src/main/java/io/opentelemetry/maven/OtelTransferListener.java @@ -0,0 +1,166 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven; + +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanBuilder; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.StatusCode; +import io.opentelemetry.maven.semconv.MavenOtelSemanticAttributes; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.UrlAttributes; +import io.opentelemetry.semconv.incubating.HttpIncubatingAttributes; +import io.opentelemetry.semconv.incubating.UrlIncubatingAttributes; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.maven.execution.ExecutionListener; +import org.apache.maven.execution.MavenSession; +import org.eclipse.aether.transfer.AbstractTransferListener; +import org.eclipse.aether.transfer.TransferEvent; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Don't mark this class as {@link javax.inject.Named} and {@link javax.inject.Singleton} because + * Maven Sisu doesn't automatically load instance of {@link ExecutionListener} as Maven Extension + * hooks the same way Maven Plexus did so we manually hook this instance of {@link + * ExecutionListener} through the {@link OtelLifecycleParticipant#afterProjectsRead(MavenSession)}. + */ +public final class OtelTransferListener extends AbstractTransferListener { + + private static final Logger logger = LoggerFactory.getLogger(OtelTransferListener.class); + + private final SpanRegistry spanRegistry; + + private final OpenTelemetrySdkService openTelemetrySdkService; + + private final Map> repositoryUriMapping = new ConcurrentHashMap<>(); + + OtelTransferListener(SpanRegistry spanRegistry, OpenTelemetrySdkService openTelemetrySdkService) { + this.spanRegistry = spanRegistry; + this.openTelemetrySdkService = openTelemetrySdkService; + } + + @Override + public void transferInitiated(TransferEvent event) { + logger.debug("OpenTelemetry: OtelTransferListener#transferInitiated({})", event); + + String httpRequestMethod; + switch (event.getRequestType()) { + case PUT: + httpRequestMethod = "PUT"; + break; + case GET: + httpRequestMethod = "GET"; + break; + case GET_EXISTENCE: + httpRequestMethod = "HEAD"; + break; + default: + logger.warn( + "OpenTelemetry: Unknown request type {} for event {}", event.getRequestType(), event); + httpRequestMethod = event.getRequestType().name(); + } + + String urlTemplate = + event.getResource().getRepositoryUrl() + + "$groupId/$artifactId/$version/$artifactId-$version.$classifier"; + + String spanName = httpRequestMethod + " " + urlTemplate; + + // Build an HTTP client span as the http call itself is not instrumented. + SpanBuilder spanBuilder = + this.openTelemetrySdkService + .getTracer() + .spanBuilder(spanName) + .setSpanKind(SpanKind.CLIENT) + .setAttribute(HttpAttributes.HTTP_REQUEST_METHOD, httpRequestMethod) + .setAttribute( + UrlAttributes.URL_PATH, + event.getResource().getRepositoryUrl() + event.getResource().getResourceName()) + .setAttribute(UrlIncubatingAttributes.URL_TEMPLATE, urlTemplate) + .setAttribute( + MavenOtelSemanticAttributes.MAVEN_TRANSFER_TYPE, event.getRequestType().name()) + .setAttribute( + MavenOtelSemanticAttributes.MAVEN_RESOURCE_NAME, + event.getResource().getResourceName()); + + repositoryUriMapping + .computeIfAbsent( + event.getResource().getRepositoryUrl(), + str -> { + try { + return str.isEmpty() ? Optional.empty() : Optional.of(new URI(str)); + } catch (URISyntaxException e) { + return Optional.empty(); + } + }) + .ifPresent( + uri -> { + spanBuilder.setAttribute(ServerAttributes.SERVER_ADDRESS, uri.getHost()); + if (uri.getPort() != -1) { + spanBuilder.setAttribute(ServerAttributes.SERVER_PORT, uri.getPort()); + } + // prevent ever increasing size + if (repositoryUriMapping.size() > 128) { + repositoryUriMapping.clear(); + } + }); + spanRegistry.putSpan(spanBuilder.startSpan(), event); + } + + @Override + public void transferSucceeded(TransferEvent event) { + logger.debug("OpenTelemetry: OtelTransferListener#transferSucceeded({})", event); + + Optional.ofNullable(spanRegistry.removeSpan(event)) + .ifPresent( + span -> { + span.setStatus(StatusCode.OK); + finish(span, event); + }); + } + + @Override + public void transferFailed(TransferEvent event) { + logger.debug("OpenTelemetry: OtelTransferListener#transferFailed({})", event); + + Optional.ofNullable(spanRegistry.removeSpan(event)).ifPresent(span -> fail(span, event)); + } + + @Override + public void transferCorrupted(TransferEvent event) { + logger.debug("OpenTelemetry: OtelTransferListener#transferCorrupted({})", event); + + Optional.ofNullable(spanRegistry.removeSpan(event)).ifPresent(span -> fail(span, event)); + } + + void finish(Span span, TransferEvent event) { + switch (event.getRequestType()) { + case PUT: + span.setAttribute( + HttpIncubatingAttributes.HTTP_REQUEST_BODY_SIZE, event.getTransferredBytes()); + break; + case GET: + case GET_EXISTENCE: + span.setAttribute( + HttpIncubatingAttributes.HTTP_RESPONSE_BODY_SIZE, event.getTransferredBytes()); + break; + } + span.end(); + } + + void fail(Span span, TransferEvent event) { + span.setStatus( + StatusCode.ERROR, + Optional.ofNullable(event.getException()).map(Exception::getMessage).orElse("n/a")); + finish(span, event); + } +} diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/SpanRegistry.java b/maven-extension/src/main/java/io/opentelemetry/maven/SpanRegistry.java index 276f1e116..85ef5c2e7 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/SpanRegistry.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/SpanRegistry.java @@ -17,6 +17,9 @@ import org.apache.maven.model.Plugin; import org.apache.maven.plugin.MojoExecution; import org.apache.maven.project.MavenProject; +import org.eclipse.aether.RepositorySystemSession; +import org.eclipse.aether.transfer.TransferEvent; +import org.eclipse.aether.transfer.TransferResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -35,6 +38,7 @@ public final class SpanRegistry { private final Map mojoExecutionKeySpanMap = new ConcurrentHashMap<>(); private final Map mavenProjectKeySpanMap = new ConcurrentHashMap<>(); + private final Map transferKeySpanMap = new ConcurrentHashMap<>(); @Nullable private Span rootSpan; /** @@ -113,6 +117,15 @@ public void putSpan(Span span, MojoExecution mojoExecution, MavenProject project } } + public void putSpan(Span span, TransferEvent event) { + TransferKey key = TransferKey.fromTransferEvent(event); + logger.debug("OpenTelemetry: putSpan({})", key); + Span previousSpanForKey = transferKeySpanMap.put(key, span); + if (previousSpanForKey != null) { + logger.warn("A span has already been started for " + key); + } + } + public Span removeSpan(MavenProject mavenProject) { logger.debug("OpenTelemetry: removeSpan({})", mavenProject); MavenProjectKey key = MavenProjectKey.fromMavenProject(mavenProject); @@ -136,6 +149,17 @@ public Span removeSpan(MojoExecution mojoExecution, MavenProject project) { return span; } + public Span removeSpan(TransferEvent event) { + TransferKey key = TransferKey.fromTransferEvent(event); + logger.debug("OpenTelemetry: removeSpan({})", key); + Span span = transferKeySpanMap.remove(key); + if (span == null) { + logger.warn("No span found for " + key); + return Span.getInvalid(); + } + return span; + } + @AutoValue abstract static class MavenProjectKey { abstract String groupId(); @@ -185,4 +209,18 @@ static MojoExecutionKey fromMojoExecution(MojoExecution mojoExecution, MavenProj MavenProjectKey.fromMavenProject(project)); } } + + @AutoValue + abstract static class TransferKey { + abstract String resourceName(); + + abstract String sessionId(); + + public static TransferKey fromTransferEvent(@Nonnull TransferEvent event) { + TransferResource resource = event.getResource(); + RepositorySystemSession session = event.getSession(); + return new AutoValue_SpanRegistry_TransferKey( + resource.getResourceName(), "session-" + System.identityHashCode(session)); + } + } } diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/ToUpperCaseTextMapGetter.java b/maven-extension/src/main/java/io/opentelemetry/maven/ToUpperCaseTextMapGetter.java new file mode 100644 index 000000000..e02b7b3e0 --- /dev/null +++ b/maven-extension/src/main/java/io/opentelemetry/maven/ToUpperCaseTextMapGetter.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven; + +import io.opentelemetry.context.propagation.TextMapGetter; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +final class ToUpperCaseTextMapGetter implements TextMapGetter> { + @Override + public Set keys(Map environmentVariables) { + return environmentVariables.keySet(); + } + + @Override + @Nullable + public String get(@Nullable Map environmentVariables, @Nonnull String key) { + return environmentVariables == null + ? null + : environmentVariables.get(key.toUpperCase(Locale.ROOT)); + } +} diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/semconv/MavenOtelSemanticAttributes.java b/maven-extension/src/main/java/io/opentelemetry/maven/semconv/MavenOtelSemanticAttributes.java index c5d821e31..446e9b87d 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/semconv/MavenOtelSemanticAttributes.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/semconv/MavenOtelSemanticAttributes.java @@ -11,6 +11,7 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.maven.OpenTelemetrySdkService; import java.util.List; +import org.eclipse.aether.transfer.TransferEvent; /** * Semantic attributes for Maven executions. @@ -36,6 +37,7 @@ public class MavenOtelSemanticAttributes { stringKey("maven.build.repository.id"); public static final AttributeKey MAVEN_BUILD_REPOSITORY_URL = stringKey("maven.build.repository.url"); + public static final AttributeKey MAVEN_EXECUTION_GOAL = stringKey("maven.execution.goal"); public static final AttributeKey MAVEN_EXECUTION_ID = stringKey("maven.execution.id"); @@ -53,6 +55,14 @@ public class MavenOtelSemanticAttributes { public static final AttributeKey MAVEN_PROJECT_VERSION = stringKey("maven.project.version"); + /** See {@link TransferEvent.RequestType}. */ + public static final AttributeKey MAVEN_TRANSFER_TYPE = + AttributeKey.stringKey("maven.transfer.type"); + + /** See {@link org.eclipse.aether.transfer.TransferResource}. */ + public static final AttributeKey MAVEN_RESOURCE_NAME = + AttributeKey.stringKey("maven.resource.name"); + public static final String SERVICE_NAME_VALUE = "maven"; // inlined incubating attribute to prevent direct dependency on incubating semconv From 55bd524d0c8a14fdd32f2c6e684d9bea60d5fae2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 10:20:06 +0300 Subject: [PATCH 027/371] fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.35.0 (#1882) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 6a0fbd7c2..430d10ba2 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.34.0") + implementation("com.google.auth:google-auth-library-oauth2-http:1.35.0") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From 60ffcf2b9c6fe141c0814a5f4e0d1a06462498ed Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 16 May 2025 07:35:13 -0700 Subject: [PATCH 028/371] Update renovate config (#1851) Co-authored-by: Lauri Tulmin --- .github/renovate.json5 | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 9425fd488..4f7743a37 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -7,18 +7,23 @@ ignorePresets: [ ':ignoreModulesAndTests', // needed to keep maven-extension test pom files up-to-date ], - prHourlyLimit: 5, + prHourlyLimit: 5, // we have a large number of parallel runners + labels: [ + 'dependencies' + ], packageRules: [ { - // this is to reduce the number of renovate PRs - matchManagers: [ - 'github-actions', - 'dockerfile', - ], - extends: [ - 'schedule:weekly', - ], - groupName: 'weekly update', + // reduces the number of Renovate PRs + // (patch updates are typically non-breaking) + "groupName": "all patch versions", + "matchUpdateTypes": ["patch"], + "schedule": ["before 8am every weekday"] + }, + { + // avoids these Renovate PRs from trickling in throughout the week + // (consolidating the review process) + "matchUpdateTypes": ["minor", "major"], + "schedule": ["before 8am on Monday"] }, { matchPackageNames: [ From 5a3c5f6cd1882a012206f3aa9d087e62d0cedfda Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 07:36:50 -0700 Subject: [PATCH 029/371] fix(deps): update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.16.0-alpha (#1886) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lauri Tulmin --- .../propagator/internal/AwsComponentProviderTest.java | 7 ++++++- cloudfoundry-resources/README.md | 2 +- dependencyManagement/build.gradle.kts | 2 +- .../jmxscraper/target_systems/JvmIntegrationTest.java | 4 ++-- .../internal/EventToSpanBridgeComponentProviderTest.java | 2 +- .../RuleBasedRoutingSamplerComponentProviderTest.java | 2 +- 6 files changed, 12 insertions(+), 7 deletions(-) diff --git a/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java b/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java index 1d1590d3a..c2869d265 100644 --- a/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java +++ b/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java @@ -20,7 +20,12 @@ class AwsComponentProviderTest { @Test void endToEnd() { - String yaml = "file_format: 0.3\n" + "propagator:\n" + " composite: [xray, xray-lambda]\n"; + String yaml = + "file_format: 0.4\n" + + "propagator:\n" + + " composite:\n" + + " - xray:\n" + + " - xray-lambda:\n"; OpenTelemetrySdk openTelemetrySdk = DeclarativeConfiguration.parseAndCreate( diff --git a/cloudfoundry-resources/README.md b/cloudfoundry-resources/README.md index 355f9ce6e..b44181d15 100644 --- a/cloudfoundry-resources/README.md +++ b/cloudfoundry-resources/README.md @@ -17,7 +17,7 @@ This variable contains a JSON structure, which is parsed to fill the following a | cloudfoundry.space.id | space_id | | cloudfoundry.space.name | space_name | -The resource attributes follow the [CloudFoundry semantic convention.](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/attributes-registry/cloudfoundry.md). +The resource attributes follow the [CloudFoundry semantic convention.](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/cloudfoundry.md). A description of `VCAP_APPLICATION` is available in the [CloudFoundry documentation](https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html#VCAP-APPLICATION). ## Component owners diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index d8cc0188b..7b3581281 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.15.0-alpha" +val otelInstrumentationVersion = "2.16.0-alpha" val semconvVersion = "1.32.0" javaPlatform { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java index 6c4069638..29b698eb5 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java @@ -32,12 +32,12 @@ protected JmxScraperContainer customizeScraperContainer( JmxScraperContainer scraper, GenericContainer target, Path tempDir) { return scraper .withTargetSystem("jvm") - // TODO when JVM metrics will be added to instrumentation, the default "auto" source + // Since JVM metrics were be added to instrumentation, the default "auto" source // means that the definitions in instrumentation will be used, and thus this test will fail // due to metrics differences, adding an explicit "legacy" source is required to continue // testing the JVM metrics defined in this project. // https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/13392 - // .withTargetSystem("legacy") + .withTargetSystemSource("legacy") // also testing custom yaml .withCustomYaml("custom-metrics.yaml"); } diff --git a/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java b/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java index a2f6165ae..55746e61d 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java @@ -18,7 +18,7 @@ class EventToSpanBridgeComponentProviderTest { @Test void endToEnd() { String yaml = - "file_format: 0.3\n" + "file_format: 0.4\n" + "logger_provider:\n" + " processors:\n" + " - event_to_span_event_bridge:\n"; diff --git a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java b/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java index 384707843..02611c9d5 100644 --- a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java +++ b/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java @@ -38,7 +38,7 @@ class RuleBasedRoutingSamplerComponentProviderTest { @Test void endToEnd() { String yaml = - "file_format: 0.3\n" + "file_format: 0.4\n" + "tracer_provider:\n" + " sampler:\n" + " parent_based:\n" From dba60a384f15143cabb18b0254a49bc7eb9e50fb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 14:37:58 +0000 Subject: [PATCH 030/371] fix(deps): update dependency org.apache.kafka:kafka-clients to v4 (#1802) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lauri Tulmin --- dependencyManagement/build.gradle.kts | 2 +- kafka-exporter/build.gradle.kts | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7b3581281..44d74220d 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -50,7 +50,7 @@ dependencies { api("org.bouncycastle:bcpkix-jdk15on:1.70") api("org.junit-pioneer:junit-pioneer:1.9.1") api("org.skyscreamer:jsonassert:1.5.3") - api("org.apache.kafka:kafka-clients:3.9.0") + api("org.apache.kafka:kafka-clients:4.0.0") api("org.testcontainers:kafka:1.21.0") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") diff --git a/kafka-exporter/build.gradle.kts b/kafka-exporter/build.gradle.kts index ad18818fe..46b1e1ebf 100644 --- a/kafka-exporter/build.gradle.kts +++ b/kafka-exporter/build.gradle.kts @@ -4,7 +4,12 @@ plugins { } description = "SpanExporter based on Kafka" -otelJava.moduleName.set("io.opentelemetry.contrib.kafka") + +otelJava { + moduleName.set("io.opentelemetry.contrib.kafka") + // kafka 4 requires java 11 + minJavaVersionSupported.set(JavaVersion.VERSION_11) +} dependencies { api("io.opentelemetry:opentelemetry-sdk-trace") From 05a4eac804a9e96d3aee5b744f5c815a6c3dc8d3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 16 May 2025 10:13:24 -0700 Subject: [PATCH 031/371] Fix broken link (#1889) --- cloudfoundry-resources/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudfoundry-resources/README.md b/cloudfoundry-resources/README.md index b44181d15..8ba62971c 100644 --- a/cloudfoundry-resources/README.md +++ b/cloudfoundry-resources/README.md @@ -17,7 +17,7 @@ This variable contains a JSON structure, which is parsed to fill the following a | cloudfoundry.space.id | space_id | | cloudfoundry.space.name | space_name | -The resource attributes follow the [CloudFoundry semantic convention.](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/cloudfoundry.md). +The resource attributes follow the [CloudFoundry semantic convention.](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/resource/cloudfoundry.md). A description of `VCAP_APPLICATION` is available in the [CloudFoundry documentation](https://docs.cloudfoundry.org/devguide/deploy-apps/environment-variable.html#VCAP-APPLICATION). ## Component owners From 3e2515164fb1ccc76473c99f76dcdd6cdc3e276c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 07:48:53 +0300 Subject: [PATCH 032/371] chore(deps): update github/codeql-action action to v3.28.18 (#1892) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a0c462705..f83006c13 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 - name: Initialize CodeQL - uses: github/codeql-action/init@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: languages: java, actions # using "latest" helps to keep up with the latest Kotlin support @@ -52,4 +52,4 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c969bba72..d8503ba1e 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@60168efe1c415ce0f5521ea06d5c2062adbeed1b # v3.28.17 + uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 with: sarif_file: results.sarif From edf87b1226e7414b26db070f4eb099209047d684 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 04:49:32 +0000 Subject: [PATCH 033/371] chore(deps): update fossas/fossa-action action to v1.7.0 (#1894) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/fossa.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index 03d4e5684..ddc524464 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -14,7 +14,7 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: fossas/fossa-action@c0a7d013f84c8ee5e910593186598625513cc1e4 # v1.6.0 + - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 with: api-key: ${{secrets.FOSSA_API_KEY}} team: OpenTelemetry From 117eabb7dfbfe27a856bf56075338e65a974e398 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 04:49:59 +0000 Subject: [PATCH 034/371] chore(deps): update gradle/actions action to v4.4.0 (#1895) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 8 ++++---- .github/workflows/codeql.yml | 2 +- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 787afe869..cebd8993d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 with: cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Gradle build and test @@ -65,7 +65,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 with: cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Gradle test @@ -87,7 +87,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 with: cache-read-only: ${{ github.event_name == 'pull_request' }} @@ -139,7 +139,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 # skipping release branches because the versions in those branches are not snapshots # (also this skips pull requests) if: ${{ github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-contrib' }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f83006c13..7906b98c5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -35,7 +35,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - name: Initialize CodeQL uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 658fbac69..3e3648f9e 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: gradle/actions/wrapper-validation@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + - uses: gradle/actions/wrapper-validation@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index dd6708163..46e12c4d6 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -27,7 +27,7 @@ jobs: run: | sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - run: ./gradlew dependencyCheckAnalyze env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f75f133db..8fd4f18c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - name: Gradle build run: ./gradlew build @@ -41,7 +41,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - name: Integration test run: ./gradlew integrationTest @@ -124,7 +124,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - name: Build and publish artifacts run: ./gradlew assemble publishToSonatype closeAndReleaseSonatypeStagingRepository env: From d1beba98e37149a4f5bc3b135b6a7b93f3f3e539 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 09:56:11 +0300 Subject: [PATCH 035/371] chore(deps): update open-telemetry/assign-reviewers-action digest to cb42e3e (#1887) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-reviewers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml index 84a7a77af..cd361767f 100644 --- a/.github/workflows/assign-reviewers.yml +++ b/.github/workflows/assign-reviewers.yml @@ -18,6 +18,6 @@ jobs: pull-requests: write # for assigning reviewers runs-on: ubuntu-latest steps: - - uses: open-telemetry/assign-reviewers-action@ab8aca8056f3b5af18282b54baa57a852c47abf8 # main + - uses: open-telemetry/assign-reviewers-action@cb42e3ee14a59c01abccd401f126a0f4c3991cb3 # main with: config-file: .github.amrom.workers.devponent_owners.yml From 0073d20a545041201ca4010073197dccec2a552d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 09:39:18 -0700 Subject: [PATCH 036/371] chore(deps): update dependency markdownlint-cli to v0.45.0 (#1893) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lauri Tulmin --- .github/workflows/reusable-markdown-lint.yml | 2 +- gcp-auth-extension/README.md | 2 +- gcp-resources/README.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-markdown-lint.yml b/.github/workflows/reusable-markdown-lint.yml index f3688c191..734bf64f1 100644 --- a/.github/workflows/reusable-markdown-lint.yml +++ b/.github/workflows/reusable-markdown-lint.yml @@ -14,4 +14,4 @@ jobs: - name: Run markdownlint run: | - npx markdownlint-cli@0.44.0 -c .github/config/markdownlint.yml **/*.md + npx markdownlint-cli@0.45.0 -c .github/config/markdownlint.yml **/*.md diff --git a/gcp-auth-extension/README.md b/gcp-auth-extension/README.md index bb2c32886..b57e55dfc 100644 --- a/gcp-auth-extension/README.md +++ b/gcp-auth-extension/README.md @@ -43,7 +43,7 @@ Here is a list of required and optional configuration available for the extensio #### Optional Config -- `GOOGLE_CLOUD_QUOTA_PROJECT`: Environment variable that represents the Google Cloud Quota Project ID which will be charged for the GCP API usage. To learn more about a *quota project*, see [here](https://cloud.google.com/docs/quotas/quota-project). Additional details about configuring the *quota project* can be found [here](https://cloud.google.com/docs/quotas/set-quota-project). +- `GOOGLE_CLOUD_QUOTA_PROJECT`: Environment variable that represents the Google Cloud Quota Project ID which will be charged for the GCP API usage. To learn more about a *quota project*, see the [Quota project overview](https://cloud.google.com/docs/quotas/quota-project) page. Additional details about configuring the *quota project* can be found on the [Set the quota project](https://cloud.google.com/docs/quotas/set-quota-project) page. - Can also be configured using `google.cloud.quota.project` system property. diff --git a/gcp-resources/README.md b/gcp-resources/README.md index 2305c0e21..6a80e1baf 100644 --- a/gcp-resources/README.md +++ b/gcp-resources/README.md @@ -55,7 +55,7 @@ For a reference example showcasing the detected resource attributes and usage wi With the release of [v2.2.0 of the OpenTelemetry Java Instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/tag/v2.2.0), the GCP resource detector is now included with the Java agent. -For users of Java Agent v2.2.0 and later, the GCP resource detectors can be enabled by following the instructions provided [here](https://opentelemetry.io/docs/languages/java/automatic/configuration/#enable-resource-providers-that-are-disabled-by-default). +For users of Java Agent v2.2.0 and later, the GCP resource detectors can be enabled by following the instructions provided in the [agent configuration documentation](https://opentelemetry.io/docs/languages/java/automatic/configuration/#enable-resource-providers-that-are-disabled-by-default). ## Component Owners From 62da4c9ced3517904363896e851c73278006746f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 19 May 2025 09:55:15 -0700 Subject: [PATCH 037/371] Update change log for upcoming release (#1888) Co-authored-by: Steve Rao --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca4f7b3ad..9351011ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,27 @@ ## Unreleased +### GCP authentication extension + +- Update the internal implementation such that the required headers are retrieved + from the Google Auth Library instead of manually constructing and passing them. + ([#1860](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1860)) + +### Inferred spans + +- Upgrade async-profiler to 4.0 + ([#1872](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1872)) + +### Kafka exporter + +- Upgrade kafka-clients to 4.0 (and so now requires Java 11+) + ([#1802](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1802)) + +### Maven extension + +- Add option to record transferred artifacts + ([#1875](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1875)) + ## Version 1.46.0 (2025-04-11) ### Baggage processor From 858912df283b187fe0879cfb93c294883b471503 Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 10:38:38 -0700 Subject: [PATCH 038/371] Update version to 1.48.0-SNAPSHOT (#1898) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 2 ++ version.gradle.kts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9351011ac..4f38cda1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## Version 1.47.0 (2025-05-19) + ### GCP authentication extension - Update the internal implementation such that the required headers are retrieved diff --git a/version.gradle.kts b/version.gradle.kts index 0ae7508cb..4d162323b 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.47.0-SNAPSHOT" -val alphaVersion = "1.47.0-alpha-SNAPSHOT" +val stableVersion = "1.48.0-SNAPSHOT" +val alphaVersion = "1.48.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From 11c3dda26b9ed5c91803f8e12e5411a272695cec Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 19 May 2025 14:06:32 -0700 Subject: [PATCH 039/371] Add spotless auto-fix for PRs (#1890) --- .github/workflows/auto-spotless-apply.yml | 95 +++++++++++++++++++++++ .github/workflows/auto-spotless-check.yml | 53 +++++++++++++ 2 files changed, 148 insertions(+) create mode 100644 .github/workflows/auto-spotless-apply.yml create mode 100644 .github/workflows/auto-spotless-check.yml diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml new file mode 100644 index 000000000..65b03f006 --- /dev/null +++ b/.github/workflows/auto-spotless-apply.yml @@ -0,0 +1,95 @@ +name: Auto spotless apply +on: + workflow_run: + workflows: + - "Auto spotless check" + types: + - completed + +permissions: + contents: read + +jobs: + apply: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + steps: + - name: Download patch + uses: actions/download-artifact@v4.3.0 + with: + run-id: ${{ github.event.workflow_run.id }} + path: ${{ runner.temp }} + merge-multiple: true + github-token: ${{ github.token }} + + - id: unzip-patch + name: Unzip patch + working-directory: ${{ runner.temp }} + run: | + if [ -f patch ]; then + echo "exists=true" >> $GITHUB_OUTPUT + fi + + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + if: steps.unzip-patch.outputs.exists == 'true' + id: otelbot-token + with: + app-id: 1296620 + private-key: ${{ secrets.OTELBOT_JAVA_CONTRIB_PRIVATE_KEY }} + + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + if: steps.unzip-patch.outputs.exists == 'true' + with: + token: ${{ steps.otelbot-token.outputs.token }} + + - id: get-pr + if: steps.unzip-patch.outputs.exists == 'true' + name: Get PR + env: + PR_BRANCH: |- + ${{ + (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) + && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) + || github.event.workflow_run.head_branch + }} + GH_TOKEN: ${{ github.token }} + run: | + echo gh pr view "${PR_BRANCH}" --json number --jq .number + number=$(gh pr view "${PR_BRANCH}" --json number --jq .number) + echo $number + echo "number=$number" >> $GITHUB_OUTPUT + + - name: Check out PR branch + if: steps.unzip-patch.outputs.exists == 'true' + env: + GH_TOKEN: ${{ github.token }} + run: gh pr checkout ${{ steps.get-pr.outputs.number }} + + - name: Use CLA approved github bot + if: steps.unzip-patch.outputs.exists == 'true' + # IMPORTANT do not call the .github/scripts/use-cla-approved-bot.sh + # since that script could have been compromised in the PR branch + run: | + git config user.name otelbot + git config user.email 197425009+otelbot@users.noreply.github.com + + - name: Apply patch and push + if: steps.unzip-patch.outputs.exists == 'true' + run: | + git apply "${{ runner.temp }}/patch" + git commit -a -m "./gradlew spotlessApply" + git push + + - if: steps.unzip-patch.outputs.exists == 'true' && success() + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + run: | + gh pr comment ${{ steps.get-pr.outputs.number }} --body "🔧 The result from spotlessApply was committed to the PR branch." + + - if: steps.unzip-patch.outputs.exists == 'true' && failure() + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + run: | + gh pr comment ${{ steps.get-pr.outputs.number }} --body "❌ The result from spotlessApply could not be committed to the PR branch, see logs: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID." diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml new file mode 100644 index 000000000..8e80f3b62 --- /dev/null +++ b/.github/workflows/auto-spotless-check.yml @@ -0,0 +1,53 @@ +name: Auto spotless check +on: + pull_request: + types: + - opened + - synchronize + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Set up JDK for running Gradle + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: 17 + + - name: Set up gradle + uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + with: + cache-read-only: true + + - name: Check out PR branch + env: + GH_TOKEN: ${{ github.token }} + run: gh pr checkout ${{ github.event.pull_request.number }} + + - name: Spotless + run: ./gradlew spotlessApply + + - id: create-patch + name: Create patch file + run: | + git diff > patch + if [ -s patch ]; then + echo "exists=true" >> "$GITHUB_OUTPUT" + fi + + - name: Upload patch file + if: steps.create-patch.outputs.exists == 'true' + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + path: patch + name: patch From 0b1780df27354a13862489f403d49b78f382388d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 May 2025 15:42:01 -0700 Subject: [PATCH 040/371] chore(deps): pin actions/download-artifact action to d3f86a1 (#1899) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/workflows/auto-spotless-apply.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 65b03f006..6312417f7 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -17,7 +17,7 @@ jobs: pull-requests: write steps: - name: Download patch - uses: actions/download-artifact@v4.3.0 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: run-id: ${{ github.event.workflow_run.id }} path: ${{ runner.temp }} From dda8cbf9d9647c2103d20f1ef3035ae3110c26b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 20 May 2025 16:16:50 +0200 Subject: [PATCH 041/371] Opamp client api (#1835) --- opamp-client/build.gradle.kts | 70 +++++++++++- .../opamp/client/internal/OpampClient.java | 102 ++++++++++++++++++ .../client/internal/response/MessageData.java | 32 ++++++ 3 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index c094faf03..a1f0c698e 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -1,11 +1,75 @@ +import de.undercouch.gradle.tasks.download.Download +import de.undercouch.gradle.tasks.download.DownloadExtension +import groovy.json.JsonSlurper + plugins { id("otel.java-conventions") + id("de.undercouch.download") version "5.6.0" + id("com.squareup.wire") version "5.3.1" } description = "Client implementation of the OpAMP spec." otelJava.moduleName.set("io.opentelemetry.contrib.opamp.client") -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 +dependencies { + annotationProcessor("com.google.auto.value:auto-value") + compileOnly("com.google.auto.value:auto-value-annotations") +} + +val opampReleaseInfo = tasks.register("opampLastReleaseInfo") { + group = "opamp" + src("https://api.github.com/repos/open-telemetry/opamp-spec/releases/latest") + dest(project.layout.buildDirectory.file("opamp/release.json")) +} + +val opampProtos = tasks.register("opampProtoDownload", download) +opampProtos.configure { + group = "opamp" + dependsOn(opampReleaseInfo) + lastReleaseInfoJson.set { + opampReleaseInfo.get().dest + } + outputProtosDir.set(project.layout.buildDirectory.dir("opamp/protos")) + downloadedZipFile.set(project.layout.buildDirectory.file("intermediate/$name/release.zip")) +} + +wire { + java {} + sourcePath { + srcDir(opampProtos) + } +} + +abstract class DownloadOpampProtos @Inject constructor( + private val download: DownloadExtension, + private val archiveOps: ArchiveOperations, + private val fileOps: FileSystemOperations, +) : DefaultTask() { + + @get:InputFile + abstract val lastReleaseInfoJson: RegularFileProperty + + @get:OutputDirectory + abstract val outputProtosDir: DirectoryProperty + + @get:Internal + abstract val downloadedZipFile: RegularFileProperty + + @Suppress("UNCHECKED_CAST") + @TaskAction + fun execute() { + val releaseInfo = JsonSlurper().parse(lastReleaseInfoJson.get().asFile) as Map + val zipUrl = releaseInfo["zipball_url"] + download.run { + src(zipUrl) + dest(downloadedZipFile) + } + val protos = archiveOps.zipTree(downloadedZipFile).matching { + setIncludes(listOf("**/*.proto")) + } + fileOps.sync { + from(protos.files) + into(outputProtosDir) + } + } } diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java new file mode 100644 index 000000000..ae12aa2e4 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal; + +import io.opentelemetry.opamp.client.internal.response.MessageData; +import opamp.proto.AgentDescription; +import opamp.proto.RemoteConfigStatus; +import opamp.proto.ServerErrorResponse; + +public interface OpampClient { + + /** + * Starts the client and begin attempts to connect to the Server. Once connection is established + * the client will attempt to maintain it by reconnecting if the connection is lost. All failed + * connection attempts will be reported via {@link Callbacks#onConnectFailed(OpampClient, + * Throwable)} callback. + * + *

This method does not wait until the connection to the Server is established and will likely + * return before the connection attempts are even made. + * + *

This method may be called only once. + * + * @param callbacks The Callback to which the Client will notify about any Server requests and + * responses. + */ + void start(Callbacks callbacks); + + /** + * Stops the client. May be called only after {@link #start(Callbacks)}. May be called only once. + * After this call returns successfully it is guaranteed that no callbacks will be called. Once + * stopped, the client cannot be started again. + */ + void stop(); + + /** + * Sets attributes of the Agent. The attributes will be included in the next status report sent to + * the Server. When called after {@link #start(Callbacks)}, the attributes will be included in the + * next outgoing status report. This is typically used by Agents which allow their + * AgentDescription to change dynamically while the OpAMPClient is started. May be also called + * from {@link Callbacks#onMessage(OpampClient, MessageData)}. + * + * @param agentDescription The new agent description. + */ + void setAgentDescription(AgentDescription agentDescription); + + /** + * Sets the current remote config status which will be sent in the next agent to server request. + * + * @param remoteConfigStatus The new remote config status. + */ + void setRemoteConfigStatus(RemoteConfigStatus remoteConfigStatus); + + interface Callbacks { + /** + * Called when the connection is successfully established to the Server. May be called after + * {@link #start(Callbacks)} is called and every time a connection is established to the Server. + * For WebSocket clients this is called after the handshake is completed without any error. For + * HTTP clients this is called for any request if the response status is OK. + * + * @param client The relevant {@link OpampClient} instance. + */ + void onConnect(OpampClient client); + + /** + * Called when the connection to the Server cannot be established. May be called after {@link + * #start(Callbacks)} is called and tries to connect to the Server. May also be called if the + * connection is lost and reconnection attempt fails. + * + * @param client The relevant {@link OpampClient} instance. + * @param throwable The exception. + */ + void onConnectFailed(OpampClient client, Throwable throwable); + + /** + * Called when the Server reports an error in response to some previously sent request. Useful + * for logging purposes. The Agent should not attempt to process the error by reconnecting or + * retrying previous operations. The client handles the ErrorResponse_UNAVAILABLE case + * internally by performing retries as necessary. + * + * @param client The relevant {@link OpampClient} instance. + * @param errorResponse The error returned by the Server. + */ + void onErrorResponse(OpampClient client, ServerErrorResponse errorResponse); + + /** + * Called when the Agent receives a message that needs processing. See {@link MessageData} + * definition for the data that may be available for processing. During onMessage execution the + * {@link OpampClient} functions that change the status of the client may be called, e.g. if + * RemoteConfig is processed then {@link #setRemoteConfigStatus(opamp.proto.RemoteConfigStatus)} + * should be called to reflect the processing result. These functions may also be called after + * onMessage returns. This is advisable if processing can take a long time. In that case + * returning quickly is preferable to avoid blocking the {@link OpampClient}. + * + * @param client The relevant {@link OpampClient} instance. + * @param messageData The server response data that needs processing. + */ + void onMessage(OpampClient client, MessageData messageData); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java new file mode 100644 index 000000000..df9fb8ba1 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.response; + +import com.google.auto.value.AutoValue; +import io.opentelemetry.opamp.client.internal.OpampClient; +import javax.annotation.Nullable; +import opamp.proto.AgentRemoteConfig; + +/** + * Data class provided in {@link OpampClient.Callbacks#onMessage(OpampClient, MessageData)} with + * Server's provided status changes. + */ +@AutoValue +public abstract class MessageData { + @Nullable + public abstract AgentRemoteConfig getRemoteConfig(); + + public static Builder builder() { + return new AutoValue_MessageData.Builder(); + } + + @AutoValue.Builder + public abstract static class Builder { + public abstract Builder setRemoteConfig(AgentRemoteConfig remoteConfig); + + public abstract MessageData build(); + } +} From 7b9925be4f35088fc4d501fcd7ff12734a234713 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 21 May 2025 10:49:42 -0700 Subject: [PATCH 042/371] Remove unused dependency (#1905) --- jmx-metrics/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index 92195ec07..a16168f2f 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -17,7 +17,6 @@ val groovyVersion = "3.0.24" dependencies { api(platform("org.codehaus.groovy:groovy-bom:$groovyVersion")) - implementation("io.grpc:grpc-netty-shaded:1.72.0") implementation("org.codehaus.groovy:groovy-jmx") implementation("org.codehaus.groovy:groovy") implementation("io.prometheus:simpleclient") From 775c5adf7e0a103d3204f311eecd9793c4065069 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 23 May 2025 09:45:49 +0300 Subject: [PATCH 043/371] chore(deps): update dependency gradle to v8.14.1 (#1906) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 247cf2a9f..9128c7d42 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=61ad310d3c7d3e5da131b76bbf22b5a4c0786e9d892dae8c1658d4b484de3caa -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionSha256Sum=845952a9d6afa783db70bb3b0effaae45ae5542ca2bb7929619e8af49cb634cf +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From f59725bffdb75f27c60d2cec48cbe659b7c90f83 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 08:02:54 +0300 Subject: [PATCH 044/371] chore(deps): update gradle/actions action to v4.4.0 (#1908) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index 8e80f3b62..b7a08afbc 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -25,7 +25,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@06832c7b30a0129d7fb559bcc6e43d26f6374244 # v4.3.1 + uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 with: cache-read-only: true From d2ebf96a6b2e08d9bd7bd30f51f7139cb5b18535 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 26 May 2025 08:03:50 +0300 Subject: [PATCH 045/371] fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.0 (#1910) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index d277ab59e..89d7ef490 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.5 + 3.5.0 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index c5cac1512..fe1e2e1e1 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.4.5 + 3.5.0 io.opentelemetry.contrib.maven.test From 98f5f837dcd3fa196422f7e159717f8fb088034b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 12:07:26 +0300 Subject: [PATCH 046/371] fix(deps): update dependency org.codehaus.groovy:groovy-bom to v3.0.25 (#1913) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- jmx-metrics/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index a16168f2f..0b864702f 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -12,7 +12,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.jmxmetrics") application.mainClass.set("io.opentelemetry.contrib.jmxmetrics.JmxMetrics") -val groovyVersion = "3.0.24" +val groovyVersion = "3.0.25" dependencies { api(platform("org.codehaus.groovy:groovy-bom:$groovyVersion")) From d169a753b38141e8bc2a51bdab0ae9304cee221a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 27 May 2025 14:23:51 -0700 Subject: [PATCH 047/371] fix(deps): update dependency io.opentelemetry.proto:opentelemetry-proto to v1.7.0-alpha (#1909) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- disk-buffering/build.gradle.kts | 2 +- gcp-auth-extension/build.gradle.kts | 2 +- jmx-metrics/build.gradle.kts | 2 +- jmx-scraper/build.gradle.kts | 2 +- kafka-exporter/build.gradle.kts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 44d74220d..936485781 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { api("com.google.errorprone:error_prone_annotations:2.38.0") api("com.google.errorprone:error_prone_core:2.38.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") - api("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha") + api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") api("io.prometheus:simpleclient:0.16.0") api("io.prometheus:simpleclient_common:0.16.0") api("io.prometheus:simpleclient_httpserver:0.16.0") diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 92a8f4e3a..ef86e9043 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -28,7 +28,7 @@ dependencies { testImplementation("org.mockito:mockito-inline") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - protos("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha@jar") + protos("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha@jar") } animalsniffer { diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 430d10ba2..94902ed9b 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -41,7 +41,7 @@ dependencies { testImplementation("org.mockito:mockito-inline") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.mock-server:mockserver-netty:5.15.0") - testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha") + testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") testImplementation("org.springframework.boot:spring-boot-starter-web:2.7.18") testImplementation("org.springframework.boot:spring-boot-starter:2.7.18") testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18") diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index 0b864702f..77ff2c243 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -47,7 +47,7 @@ testing { dependencies { implementation("com.linecorp.armeria:armeria-grpc") implementation("com.linecorp.armeria:armeria-junit5") - implementation("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha") + implementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") implementation("org.testcontainers:junit-jupiter") implementation("org.slf4j:slf4j-simple") } diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 0d174717e..ba40ddcac 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -39,7 +39,7 @@ testing { implementation("org.slf4j:slf4j-simple") implementation("com.linecorp.armeria:armeria-junit5") implementation("com.linecorp.armeria:armeria-grpc") - implementation("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha") + implementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") implementation("org.bouncycastle:bcprov-jdk18on:1.80") implementation("org.bouncycastle:bcpkix-jdk18on:1.80") } diff --git a/kafka-exporter/build.gradle.kts b/kafka-exporter/build.gradle.kts index 46b1e1ebf..d4aed3f54 100644 --- a/kafka-exporter/build.gradle.kts +++ b/kafka-exporter/build.gradle.kts @@ -14,7 +14,7 @@ otelJava { dependencies { api("io.opentelemetry:opentelemetry-sdk-trace") api("io.opentelemetry:opentelemetry-sdk-common") - api("io.opentelemetry.proto:opentelemetry-proto:1.5.0-alpha") + api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") api("org.apache.kafka:kafka-clients") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") From d7185495dee8cbb04d2408664d120ce2799e93d7 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 27 May 2025 23:24:20 +0200 Subject: [PATCH 048/371] add link to new jvm metrics for jmx-scraper (#1914) --- jmx-scraper/README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 31fd8024d..2f19ceab9 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -58,20 +58,20 @@ If there is a need to override existing ready-to-use metrics or to keep control Supported values for `otel.jmx.target.system` and support for `otel.jmx.target.source` and links to the metrics definitions: -| `otel.jmx.target.system` | description | `legacy` | `instrumentation` | -|--------------------------|-----------------------|-----------------------------------------------------------------|-------------------| -| `activemq` | Apache ActiveMQ | [`activemq.yaml`](src/main/resources/activemq.yaml) | | -| `cassandra` | Apache Cassandra | [`cassandra.yaml`](src/main/resources/cassandra.yaml) | | -| `hbase` | Apache HBase | [`hbase.yaml`](src/main/resources/hbase.yaml) | | -| `hadoop` | Apache Hadoop | [`hadoop.yaml`](src/main/resources/hadoop.yaml) | | -| `jetty` | Eclipse Jetty | [`jetty.yaml`](src/main/resources/jetty.yaml) | | -| `jvm` | JVM runtime metrics | [`jvm.yaml`](src/main/resources/jvm.yaml) | | -| `kafka` | Apache Kafka | [`kafka.yaml`](src/main/resources/kafka.yaml) | | -| `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | -| `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | -| `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | -| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | | -| `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | | +| `otel.jmx.target.system` | description | `legacy` | `instrumentation` | +|--------------------------|-----------------------|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `activemq` | Apache ActiveMQ | [`activemq.yaml`](src/main/resources/activemq.yaml) | | +| `cassandra` | Apache Cassandra | [`cassandra.yaml`](src/main/resources/cassandra.yaml) | | +| `hbase` | Apache HBase | [`hbase.yaml`](src/main/resources/hbase.yaml) | | +| `hadoop` | Apache Hadoop | [`hadoop.yaml`](src/main/resources/hadoop.yaml) | | +| `jetty` | Eclipse Jetty | [`jetty.yaml`](src/main/resources/jetty.yaml) | | +| `jvm` | JVM runtime metrics | [`jvm.yaml`](src/main/resources/jvm.yaml) | [`jvm.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jvm.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jvm.md)) | +| `kafka` | Apache Kafka | [`kafka.yaml`](src/main/resources/kafka.yaml) | | +| `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | +| `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | +| `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | +| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | | +| `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | | The source of metrics definitions is controlled by `otel.jmx.target.source`: From 1ea60283f904cf7cde8f24f1c0ba67f485ac845a Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 27 May 2025 18:27:34 -0400 Subject: [PATCH 049/371] Add metrics support to auth extension (#1891) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- gcp-auth-extension/README.md | 4 + .../contrib/gcp/auth/ConfigurableOption.java | 22 +- ...thAutoConfigurationCustomizerProvider.java | 84 ++- ...toConfigurationCustomizerProviderTest.java | 606 +++++++++++++++++- 4 files changed, 686 insertions(+), 30 deletions(-) diff --git a/gcp-auth-extension/README.md b/gcp-auth-extension/README.md index b57e55dfc..209283daa 100644 --- a/gcp-auth-extension/README.md +++ b/gcp-auth-extension/README.md @@ -47,6 +47,10 @@ Here is a list of required and optional configuration available for the extensio - Can also be configured using `google.cloud.quota.project` system property. +- `GOOGLE_OTEL_AUTH_TARGET_SIGNALS`: Environment variable that specifies a comma-separated list of OpenTelemetry signals for which this authentication extension should be active. Valid values contain - `metrics`, `traces` or `all`. If left unspecified, `all` is assumed meaning the extension will attempt to apply authentication to exports for all signals. + + - Can also be configured using `google.otel.auth.target.signals` system property. + ## Usage ### With OpenTelemetry Java agent 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 7928f9ab4..5f4a627e1 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 @@ -30,7 +30,27 @@ public enum ConfigurableOption { * href="https://cloud.google.com/docs/quotas/set-quota-project">official GCP client * libraries. */ - GOOGLE_CLOUD_QUOTA_PROJECT("Google Cloud Quota Project ID"); + GOOGLE_CLOUD_QUOTA_PROJECT("Google Cloud Quota Project ID"), + + /** + * Specifies a comma-separated list of OpenTelemetry signals for which this authentication + * extension should be active. The authentication mechanisms provided by this extension will only + * be applied to the listed signals. If not set, {@code all} is assumed to be set which means + * authentication is enabled for all supported signals. + * + *

Valid signal values are: + * + *

    + *
  • {@code metrics} - Enables authentication for metric exports. + *
  • {@code traces} - Enables authentication for trace exports. + *
  • {@code all} - Enables authentication for all exports. + *
+ * + *

The values are case-sensitive. Whitespace around commas and values is ignored. Can be + * configured using the environment variable `GOOGLE_OTEL_AUTH_TARGET_SIGNALS` or the system + * property `google.otel.auth.target.signals`. + */ + GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Auth Extension"); private final String userReadableName; private final String environmentVariableName; 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 053aeef7d..d6c68e54f 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 @@ -10,21 +10,28 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; +import javax.annotation.Nonnull; /** * An AutoConfigurationCustomizerProvider for Google Cloud Platform (GCP) OpenTelemetry (OTLP) @@ -46,13 +53,29 @@ public class GcpAuthAutoConfigurationCustomizerProvider static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project"; static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id"; + static final String SIGNAL_TYPE_TRACES = "traces"; + static final String SIGNAL_TYPE_METRICS = "metrics"; + static final String SIGNAL_TYPE_ALL = "all"; + /** - * Customizes the provided {@link AutoConfigurationCustomizer}. + * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to + * GCP Telemetry API are possible from the configured OTLP exporter. * *

This method attempts to retrieve Google Application Default Credentials (ADC) and performs - * the following: - Adds authorization headers to the configured {@link SpanExporter} based on the - * retrieved credentials. - Adds default properties for OTLP endpoint and resource attributes for - * GCP integration. + * the following: + * + *

    + *
  • Verifies whether the configured OTLP endpoint (base or signal specific) is a known GCP + * endpoint. + *
  • If the configured base OTLP endpoint is a known GCP Telemetry API endpoint, customizes + * both the configured OTLP {@link SpanExporter} and {@link MetricExporter}. + *
  • If the configured signal specific endpoint is a known GCP Telemetry API endpoint, + * 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 + * enable GCP integration. * * @param autoConfiguration the AutoConfigurationCustomizer to customize. * @throws GoogleAuthException if there's an error retrieving Google Application Default @@ -61,7 +84,7 @@ public class GcpAuthAutoConfigurationCustomizerProvider * not configured through environment variables or system properties. */ @Override - public void customize(AutoConfigurationCustomizer autoConfiguration) { + public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { GoogleCredentials credentials; try { credentials = GoogleCredentials.getApplicationDefault(); @@ -70,7 +93,10 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { } autoConfiguration .addSpanExporterCustomizer( - (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials)) + (spanExporter, configProperties) -> customizeSpanExporter(spanExporter, credentials)) + .addMetricExporterCustomizer( + (metricExporter, configProperties) -> + customizeMetricExporter(metricExporter, credentials)) .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); } @@ -79,6 +105,34 @@ public int order() { return Integer.MAX_VALUE - 1; } + private static SpanExporter customizeSpanExporter( + SpanExporter exporter, GoogleCredentials credentials) { + if (isSignalTargeted(SIGNAL_TYPE_TRACES)) { + return addAuthorizationHeaders(exporter, credentials); + } + return exporter; + } + + private static MetricExporter customizeMetricExporter( + MetricExporter exporter, GoogleCredentials credentials) { + if (isSignalTargeted(SIGNAL_TYPE_METRICS)) { + return addAuthorizationHeaders(exporter, credentials); + } + return exporter; + } + + // Checks if the auth extension is configured to target the passed signal for authentication. + private static boolean isSignalTargeted(String checkSignal) { + String userSpecifiedTargetedSignals = + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback( + () -> SIGNAL_TYPE_ALL); + return Arrays.stream(userSpecifiedTargetedSignals.split(",")) + .map(String::trim) + .anyMatch( + targetedSignal -> + targetedSignal.equals(checkSignal) || targetedSignal.equals(SIGNAL_TYPE_ALL)); + } + // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and // OtlpHttpSpanExporter. private static SpanExporter addAuthorizationHeaders( @@ -97,6 +151,24 @@ private static SpanExporter addAuthorizationHeaders( return exporter; } + // Adds authorization headers to the calls made by the OtlpGrpcMetricExporter and + // OtlpHttpMetricExporter. + private static MetricExporter addAuthorizationHeaders( + MetricExporter exporter, GoogleCredentials credentials) { + if (exporter instanceof OtlpHttpMetricExporter) { + OtlpHttpMetricExporterBuilder builder = + ((OtlpHttpMetricExporter) exporter) + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + return builder.build(); + } else if (exporter instanceof OtlpGrpcMetricExporter) { + OtlpGrpcMetricExporterBuilder builder = + ((OtlpGrpcMetricExporter) exporter) + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + return builder.build(); + } + return exporter; + } + private static Map getRequiredHeaderMap(GoogleCredentials credentials) { Map> gcpHeaders; try { diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 5cbee0890..de96dc382 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -7,6 +7,9 @@ import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.GCP_USER_PROJECT_ID_KEY; import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_ALL; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_METRICS; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_TRACES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -17,10 +20,16 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Scope; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.OpenTelemetrySdk; @@ -31,8 +40,15 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; @@ -45,10 +61,12 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Stream; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -63,19 +81,24 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; @ExtendWith(MockitoExtension.class) class GcpAuthAutoConfigurationCustomizerProviderTest { private static final String DUMMY_GCP_RESOURCE_PROJECT_ID = "my-gcp-resource-project-id"; private static final String DUMMY_GCP_QUOTA_PROJECT_ID = "my-gcp-quota-project-id"; + private static final Random TEST_RANDOM = new Random(); @Mock private GoogleCredentials mockedGoogleCredentials; - @Captor private ArgumentCaptor>> headerSupplierCaptor; + @Captor private ArgumentCaptor>> traceHeaderSupplierCaptor; + @Captor private ArgumentCaptor>> metricHeaderSupplierCaptor; - private static final ImmutableMap otelProperties = + private static final ImmutableMap defaultOtelPropertiesSpanExporter = ImmutableMap.of( + "otel.exporter.otlp.traces.endpoint", + "https://telemetry.googleapis.com/v1/traces", "otel.traces.exporter", "otlp", "otel.metrics.exporter", @@ -85,16 +108,32 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { "otel.resource.attributes", "foo=bar"); + private static final ImmutableMap defaultOtelPropertiesMetricExporter = + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://telemetry.googleapis.com/v1/metrics", + "otel.traces.exporter", + "none", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none", + "otel.resource.attributes", + "foo=bar"); + @BeforeEach public void setup() { MockitoAnnotations.openMocks(this); } + // TODO: Use parameterized test for testing traces customizer for http & grpc. @Test - public void testCustomizerOtlpHttp() { + public void testTraceCustomizerOtlpHttp() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_TRACES); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpHttpSpanExporter mockOtlpHttpSpanExporter = Mockito.mock(OtlpHttpSpanExporter.class); @@ -113,6 +152,7 @@ public void testCustomizerOtlpHttp() { }); Mockito.when(mockOtlpHttpSpanExporter.toBuilder()).thenReturn(spyOtlpHttpSpanExporterBuilder); + // begin assertions try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { googleCredentialsMockedStatic @@ -127,9 +167,10 @@ public void testCustomizerOtlpHttp() { Mockito.verify(mockOtlpHttpSpanExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpHttpSpanExporterBuilder, Mockito.times(1)) - .setHeaders(headerSupplierCaptor.capture()); - assertEquals(2, headerSupplierCaptor.getValue().get().size()); - assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue(); + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); Mockito.verify(mockOtlpHttpSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); @@ -149,19 +190,22 @@ public void testCustomizerOtlpHttp() { } @Test - public void testCustomizerOtlpGrpc() { + public void testTraceCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_TRACES); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - configureGrpcMockExporters( + configureGrpcMockSpanExporter( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + // begin assertions try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { googleCredentialsMockedStatic @@ -176,9 +220,10 @@ public void testCustomizerOtlpGrpc() { Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) - .setHeaders(headerSupplierCaptor.capture()); - assertEquals(2, headerSupplierCaptor.getValue().get().size()); - assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue(); + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); @@ -197,8 +242,133 @@ public void testCustomizerOtlpGrpc() { } } + // TODO: Use parameterized test for testing metrics customizer for http & grpc. + @Test + public void testMetricCustomizerOtlpHttp() { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), + SIGNAL_TYPE_METRICS); + // Prepare mocks + prepareMockBehaviorForGoogleCredentials(); + OtlpHttpMetricExporter mockOtlpHttpMetricExporter = Mockito.mock(OtlpHttpMetricExporter.class); + OtlpHttpMetricExporterBuilder otlpMetricExporterBuilder = OtlpHttpMetricExporter.builder(); + OtlpHttpMetricExporterBuilder spyOtlpHttpMetricExporterBuilder = + Mockito.spy(otlpMetricExporterBuilder); + List exportedMetrics = new ArrayList<>(); + configureHttpMockMetricExporter( + mockOtlpHttpMetricExporter, spyOtlpHttpMetricExporterBuilder, exportedMetrics); + + // begin assertions + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpHttpMetricExporter); + generateTestMetric(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + + Mockito.verify(mockOtlpHttpMetricExporter, Mockito.times(1)).toBuilder(); + Mockito.verify(spyOtlpHttpMetricExporterBuilder, Mockito.times(1)) + .setHeaders(metricHeaderSupplierCaptor.capture()); + assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) + .isTrue(); + + Mockito.verify(mockOtlpHttpMetricExporter, Mockito.atLeast(1)) + .export(Mockito.anyCollection()); + + assertThat(exportedMetrics) + .hasSizeGreaterThan(0) + .allSatisfy( + metricData -> { + assertThat(metricData.getResource().getAttributes().asMap()) + .containsEntry( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), + DUMMY_GCP_RESOURCE_PROJECT_ID) + .containsEntry(AttributeKey.stringKey("foo"), "bar"); + assertThat(metricData.getLongSumData().getPoints()) + .hasSizeGreaterThan(0) + .allSatisfy( + longPointData -> { + assertThat(longPointData.getAttributes().asMap()) + .containsKey(AttributeKey.longKey("work_loop")); + }); + }); + } + } + + @Test + public void testMetricCustomizerOtlpGrpc() { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), + SIGNAL_TYPE_METRICS); + // Prepare mocks + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter = Mockito.mock(OtlpGrpcMetricExporter.class); + OtlpGrpcMetricExporterBuilder otlpMetricExporterBuilder = OtlpGrpcMetricExporter.builder(); + OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder = + Mockito.spy(otlpMetricExporterBuilder); + List exportedMetrics = new ArrayList<>(); + configureGrpcMockMetricExporter( + mockOtlpGrpcMetricExporter, spyOtlpGrpcMetricExporterBuilder, exportedMetrics); + + // begin assertions + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcMetricExporter); + generateTestMetric(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + + Mockito.verify(mockOtlpGrpcMetricExporter, Mockito.times(1)).toBuilder(); + Mockito.verify(spyOtlpGrpcMetricExporterBuilder, Mockito.times(1)) + .setHeaders(metricHeaderSupplierCaptor.capture()); + assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) + .isTrue(); + + Mockito.verify(mockOtlpGrpcMetricExporter, Mockito.atLeast(1)) + .export(Mockito.anyCollection()); + + assertThat(exportedMetrics) + .hasSizeGreaterThan(0) + .allSatisfy( + metricData -> { + assertThat(metricData.getResource().getAttributes().asMap()) + .containsEntry( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), + DUMMY_GCP_RESOURCE_PROJECT_ID) + .containsEntry(AttributeKey.stringKey("foo"), "bar"); + assertThat(metricData.getLongSumData().getPoints()) + .hasSizeGreaterThan(0) + .allSatisfy( + longPointData -> { + assertThat(longPointData.getAttributes().asMap()) + .containsKey(AttributeKey.longKey("work_loop")); + }); + }); + } + } + @Test public void testCustomizerFailWithMissingResourceProject() { + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { @@ -219,6 +389,8 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); // Prepare request metadata AccessToken fakeAccessToken = new AccessToken("fake", Date.from(Instant.now())); @@ -252,7 +424,7 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - configureGrpcMockExporters( + configureGrpcMockSpanExporter( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = @@ -268,10 +440,10 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); assertTrue(joinResult.isSuccess()); Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) - .setHeaders(headerSupplierCaptor.capture()); + .setHeaders(traceHeaderSupplierCaptor.capture()); // assert that the Authorization bearer token header is present - Map exportHeaders = headerSupplierCaptor.getValue().get(); + Map exportHeaders = traceHeaderSupplierCaptor.getValue().get(); assertThat(exportHeaders).containsEntry("Authorization", "Bearer fake"); if (testCase.getExpectedQuotaProjectInHeader() == null) { @@ -285,6 +457,215 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws } } + @ParameterizedTest + @MethodSource("provideTargetSignalBehaviorTestCases") + public void testTargetSignalsBehavior(TargetSignalBehavior testCase) { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Prepare mocks + // Prepare mocked credential + prepareMockBehaviorForGoogleCredentials(); + + // Prepare mocked span exporter + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + // Prepare mocked metrics exporter + OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter = Mockito.mock(OtlpGrpcMetricExporter.class); + OtlpGrpcMetricExporterBuilder otlpMetricExporterBuilder = OtlpGrpcMetricExporter.builder(); + OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder = + Mockito.spy(otlpMetricExporterBuilder); + List exportedMetrics = new ArrayList<>(); + configureGrpcMockMetricExporter( + mockOtlpGrpcMetricExporter, spyOtlpGrpcMetricExporterBuilder, exportedMetrics); + + // configure environment according to test case + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), + testCase.getConfiguredTargetSignals()); + + // Build Autoconfigured OpenTelemetry SDK using the mocks and send signals + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + OpenTelemetrySdk sdk = + buildOpenTelemetrySdkWithExporter( + mockOtlpGrpcSpanExporter, + mockOtlpGrpcMetricExporter, + testCase.getUserSpecifiedOtelProperties()); + generateTestMetric(sdk); + generateTestSpan(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + + // Check Traces modification conditions + if (testCase.getExpectedIsTraceSignalModified()) { + // If traces signal is expected to be modified, auth headers must be present + Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); + } else { + // If traces signals is not expected to be modified then no interaction with the builder + // should be made + Mockito.verifyNoInteractions(spyOtlpGrpcSpanExporterBuilder); + } + + // Check Metric modification conditions + if (testCase.getExpectedIsMetricsSignalModified()) { + // If metrics signal is expected to be modified, auth headers must be present + Mockito.verify(spyOtlpGrpcMetricExporterBuilder, Mockito.times(1)) + .setHeaders(metricHeaderSupplierCaptor.capture()); + assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) + .isTrue(); + } else { + // If metrics signals is not expected to be modified then no interaction with the builder + // should be made + Mockito.verifyNoInteractions(spyOtlpGrpcMetricExporterBuilder); + } + } + } + + /** Test cases specifying expected behavior for GOOGLE_OTEL_AUTH_TARGET_SIGNALS */ + private static Stream provideTargetSignalBehaviorTestCases() { + return Stream.of( + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("traces") + .setUserSpecifiedOtelProperties(defaultOtelPropertiesSpanExporter) + .setExpectedIsMetricsSignalModified(false) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metrics") + .setUserSpecifiedOtelProperties(defaultOtelPropertiesMetricExporter) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(false) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("all") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metrics, traces") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("all") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "none", + "otel.metrics.exporter", + "none", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(false) + .setExpectedIsTraceSignalModified(false) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metric, trace") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(false) + .setExpectedIsTraceSignalModified(false) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metrics, trace") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(false) + .build())); + } + /** * Test cases specifying expected value for the user quota project header given the user input and * the current credentials state. @@ -356,16 +737,20 @@ private static Stream provideQuotaBehaviorTestCases() { .build())); } - // Configure necessary behavior on the Grpc mock exporters to work - // TODO: Potential improvement - make this work for Http exporter as well. - private static void configureGrpcMockExporters( + // Configure necessary behavior on the gRPC mock span exporters to work. + // Mockito.lenient is used here because this method is used with parameterized tests where based + // on certain inputs, certain stubbings may not be required. + private static void configureGrpcMockSpanExporter( OtlpGrpcSpanExporter mockGrpcExporter, OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder, List exportedSpanContainer) { - Mockito.when(spyGrpcExporterBuilder.build()).thenReturn(mockGrpcExporter); - Mockito.when(mockGrpcExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); - Mockito.when(mockGrpcExporter.toBuilder()).thenReturn(spyGrpcExporterBuilder); - Mockito.when(mockGrpcExporter.export(Mockito.anyCollection())) + Mockito.lenient().when(spyGrpcExporterBuilder.build()).thenReturn(mockGrpcExporter); + Mockito.lenient() + .when(mockGrpcExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + Mockito.lenient().when(mockGrpcExporter.toBuilder()).thenReturn(spyGrpcExporterBuilder); + Mockito.lenient() + .when(mockGrpcExporter.export(Mockito.anyCollection())) .thenAnswer( invocationOnMock -> { exportedSpanContainer.addAll(invocationOnMock.getArgument(0)); @@ -373,6 +758,88 @@ private static void configureGrpcMockExporters( }); } + // Configure necessary behavior on the http mock metric exporters to work. + private static void configureHttpMockMetricExporter( + OtlpHttpMetricExporter mockOtlpHttpMetricExporter, + OtlpHttpMetricExporterBuilder spyOtlpHttpMetricExporterBuilder, + List exportedMetricContainer) { + Mockito.when(spyOtlpHttpMetricExporterBuilder.build()).thenReturn(mockOtlpHttpMetricExporter); + Mockito.when(mockOtlpHttpMetricExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + Mockito.when(mockOtlpHttpMetricExporter.toBuilder()) + .thenReturn(spyOtlpHttpMetricExporterBuilder); + Mockito.when(mockOtlpHttpMetricExporter.export(Mockito.anyCollection())) + .thenAnswer( + invocationOnMock -> { + exportedMetricContainer.addAll(invocationOnMock.getArgument(0)); + return CompletableResultCode.ofSuccess(); + }); + // mock the get default aggregation and aggregation temporality - they're required for valid + // metric collection. + Mockito.when(mockOtlpHttpMetricExporter.getDefaultAggregation(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpHttpMetricExporter.getDefault().getDefaultAggregation(instrumentType); + }); + Mockito.when(mockOtlpHttpMetricExporter.getAggregationTemporality(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpHttpMetricExporter.getDefault() + .getAggregationTemporality(instrumentType); + }); + } + + // Configure necessary behavior on the gRPC mock metrics exporters to work. + // Mockito.lenient is used here because this method is used with parameterized tests where based + // on certain inputs, certain stubbings may not be required. + private static void configureGrpcMockMetricExporter( + OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter, + OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder, + List exportedMetricContainer) { + Mockito.lenient() + .when(spyOtlpGrpcMetricExporterBuilder.build()) + .thenReturn(mockOtlpGrpcMetricExporter); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.toBuilder()) + .thenReturn(spyOtlpGrpcMetricExporterBuilder); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.export(Mockito.anyCollection())) + .thenAnswer( + invocationOnMock -> { + exportedMetricContainer.addAll(invocationOnMock.getArgument(0)); + return CompletableResultCode.ofSuccess(); + }); + // mock the get default aggregation and aggregation temporality - they're required for valid + // metric collection. + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.getDefaultAggregation(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpGrpcMetricExporter.getDefault().getDefaultAggregation(instrumentType); + }); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.getAggregationTemporality(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpGrpcMetricExporter.getDefault() + .getAggregationTemporality(instrumentType); + }); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.getMemoryMode()) + .thenReturn(MemoryMode.IMMUTABLE_DATA); + } + @AutoValue abstract static class QuotaProjectIdTestBehavior { // A null user specified quota represents the use case where user omits specifying quota @@ -409,11 +876,48 @@ abstract static class Builder { } } + @AutoValue + abstract static class TargetSignalBehavior { + @Nonnull + abstract String getConfiguredTargetSignals(); + + @Nonnull + abstract ImmutableMap getUserSpecifiedOtelProperties(); + + abstract boolean getExpectedIsTraceSignalModified(); + + abstract boolean getExpectedIsMetricsSignalModified(); + + static Builder builder() { + return new AutoValue_GcpAuthAutoConfigurationCustomizerProviderTest_TargetSignalBehavior + .Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setConfiguredTargetSignals(String targetSignals); + + abstract Builder setUserSpecifiedOtelProperties(Map oTelProperties); + + // Set whether the combination of specified OTel properties and configured target signals + // should lead to modification of the OTLP trace exporters. + abstract Builder setExpectedIsTraceSignalModified(boolean expectedModified); + + // Set whether the combination of specified OTel properties and configured target signals + // should lead to modification of the OTLP metrics exporters. + abstract Builder setExpectedIsMetricsSignalModified(boolean expectedModified); + + abstract TargetSignalBehavior build(); + } + } + + // Mockito.lenient is used here because this method is used with parameterized tests where based @SuppressWarnings("CannotMockMethod") private void prepareMockBehaviorForGoogleCredentials() { AccessToken fakeAccessToken = new AccessToken("fake", Date.from(Instant.now())); try { - Mockito.when(mockedGoogleCredentials.getRequestMetadata()) + Mockito.lenient() + .when(mockedGoogleCredentials.getRequestMetadata()) .thenReturn( ImmutableMap.of( "Authorization", @@ -426,10 +930,38 @@ private void prepareMockBehaviorForGoogleCredentials() { } private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) { + return buildOpenTelemetrySdkWithExporter( + spanExporter, OtlpHttpMetricExporter.getDefault(), defaultOtelPropertiesSpanExporter); + } + + @SuppressWarnings("UnusedMethod") + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( + SpanExporter spanExporter, ImmutableMap customOTelProperties) { + return buildOpenTelemetrySdkWithExporter( + spanExporter, OtlpHttpMetricExporter.getDefault(), customOTelProperties); + } + + @SuppressWarnings("UnusedMethod") + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(MetricExporter metricExporter) { + return buildOpenTelemetrySdkWithExporter( + OtlpHttpSpanExporter.getDefault(), metricExporter, defaultOtelPropertiesMetricExporter); + } + + @SuppressWarnings("UnusedMethod") + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( + MetricExporter metricExporter, ImmutableMap customOtelProperties) { + return buildOpenTelemetrySdkWithExporter( + OtlpHttpSpanExporter.getDefault(), metricExporter, customOtelProperties); + } + + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( + SpanExporter spanExporter, + MetricExporter metricExporter, + ImmutableMap customOtelProperties) { SpiHelper spiHelper = SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader()); AutoConfiguredOpenTelemetrySdkBuilder builder = - AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> otelProperties); + AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> customOtelProperties); AutoConfigureUtil.setComponentLoader( builder, new ComponentLoader() { @@ -451,6 +983,21 @@ public String getName() { } }); } + if (spiClass == ConfigurableMetricExporterProvider.class) { + return Collections.singletonList( + (T) + new ConfigurableMetricExporterProvider() { + @Override + public MetricExporter createExporter(ConfigProperties configProperties) { + return metricExporter; + } + + @Override + public String getName() { + return "otlp"; + } + }); + } return spiHelper.load(spiClass); } }); @@ -476,6 +1023,19 @@ private static void generateTestSpan(OpenTelemetrySdk openTelemetrySdk) { } } + private static void generateTestMetric(OpenTelemetrySdk openTelemetrySdk) { + LongCounter longCounter = + openTelemetrySdk + .getMeter("test") + .counterBuilder("sample") + .setDescription("sample counter") + .setUnit("1") + .build(); + long workOutput = busyloop(); + long randomValue = TEST_RANDOM.nextInt(1000); + longCounter.add(randomValue, Attributes.of(AttributeKey.longKey("work_loop"), workOutput)); + } + // loop to simulate work done private static long busyloop() { Instant start = Instant.now(); From 8871a07a113f2c3e70fc4790814ecd4d90bd9bbc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 02:19:04 +0000 Subject: [PATCH 050/371] fix(deps): update spotless packages to v7.0.4 (#1916) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 80cadb3fb..9e137dc98 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "7.0.3" + id("com.diffplug.spotless") version "7.0.4" } repositories { @@ -12,7 +12,7 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.3") + implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.4") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.2.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.1") From fc6507e5e24473fc4a131e3bda1d4b1156fe18c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 28 May 2025 09:06:27 +0300 Subject: [PATCH 051/371] chore(deps): update plugin com.gradle.develocity to v4.0.2 (#1915) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index ab00e263f..27db34c41 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.github.johnrengelman.shadow") version "8.1.1" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.0.1" + id("com.gradle.develocity") version "4.0.2" } } From f597cbe4ab11d591e702c7304ee27f6008bb34f6 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Thu, 29 May 2025 01:53:02 +0200 Subject: [PATCH 052/371] Shared storage (#1912) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .../buffering/LogRecordFromDiskExporter.java | 12 ++--- .../buffering/LogRecordToDiskExporter.java | 13 ++--- .../buffering/MetricFromDiskExporter.java | 10 ++-- .../disk/buffering/MetricToDiskExporter.java | 13 ++--- .../disk/buffering/SpanFromDiskExporter.java | 10 ++-- .../disk/buffering/SpanToDiskExporter.java | 13 ++--- .../exporter/FromDiskExporterBuilder.java | 46 ++++------------ .../exporter/FromDiskExporterImpl.java | 10 ++-- .../internal/exporter/ToDiskExporter.java | 11 ++-- .../exporter/ToDiskExporterBuilder.java | 54 +++---------------- .../logs/models/LogRecordDataImpl.java | 12 ----- .../buffering/internal/storage/Storage.java | 20 +++++-- .../internal/storage/StorageBuilder.java | 19 ++++--- .../internal/storage/files/WritableFile.java | 4 ++ .../buffering/FromDiskExporterImplTest.java | 9 ++-- .../disk/buffering/IntegrationTest.java | 43 +++++++-------- .../buffering/SpanFromDiskExporterTest.java | 38 ++++++------- .../exporter/ToDiskExporterBuilderTest.java | 32 ----------- .../internal/exporter/ToDiskExporterTest.java | 2 +- .../internal/storage/StorageTest.java | 20 +++++++ .../buffering/internal/storage/TestData.java | 12 +++++ 21 files changed, 154 insertions(+), 249 deletions(-) delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilderTest.java diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java index 7b37ee361..c26a383d6 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java @@ -5,11 +5,10 @@ package io.opentelemetry.contrib.disk.buffering; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.export.LogRecordExporter; import java.io.IOException; @@ -19,15 +18,12 @@ public class LogRecordFromDiskExporter implements FromDiskExporter { private final FromDiskExporterImpl delegate; - public static LogRecordFromDiskExporter create( - LogRecordExporter exporter, StorageConfiguration config) throws IOException { + public static LogRecordFromDiskExporter create(LogRecordExporter exporter, Storage storage) + throws IOException { FromDiskExporterImpl delegate = - FromDiskExporterImpl.builder() - .setFolderName(SignalTypes.logs.name()) - .setStorageConfiguration(config) + FromDiskExporterImpl.builder(storage) .setDeserializer(SignalDeserializer.ofLogs()) .setExportFunction(exporter::export) - .setDebugEnabled(config.isDebugEnabled()) .build(); return new LogRecordFromDiskExporter(delegate); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java index 7570aed8e..665e90f76 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java @@ -5,10 +5,9 @@ package io.opentelemetry.contrib.disk.buffering; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.export.LogRecordExporter; @@ -26,16 +25,12 @@ public class LogRecordToDiskExporter implements LogRecordExporter { * Creates a new LogRecordToDiskExporter that will buffer LogRecordData telemetry on disk storage. * * @param delegate - The LogRecordExporter to delegate to if disk writing fails. - * @param config - The StorageConfiguration that specifies how storage is managed. + * @param storage - The Storage instance that specifies how storage is managed. * @return A new LogRecordToDiskExporter instance. - * @throws IOException if the delegate ToDiskExporter could not be created. */ - public static LogRecordToDiskExporter create( - LogRecordExporter delegate, StorageConfiguration config) throws IOException { + public static LogRecordToDiskExporter create(LogRecordExporter delegate, Storage storage) { ToDiskExporter toDisk = - ToDiskExporter.builder() - .setFolderName(SignalTypes.logs.name()) - .setStorageConfiguration(config) + ToDiskExporter.builder(storage) .setSerializer(SignalSerializer.ofLogs()) .setExportFunction(delegate::export) .build(); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java index bf652f8f8..8bb4f3dcd 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java @@ -5,11 +5,10 @@ package io.opentelemetry.contrib.disk.buffering; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; import java.io.IOException; @@ -19,15 +18,12 @@ public class MetricFromDiskExporter implements FromDiskExporter { private final FromDiskExporterImpl delegate; - public static MetricFromDiskExporter create(MetricExporter exporter, StorageConfiguration config) + public static MetricFromDiskExporter create(MetricExporter exporter, Storage storage) throws IOException { FromDiskExporterImpl delegate = - FromDiskExporterImpl.builder() - .setFolderName(SignalTypes.metrics.name()) - .setStorageConfiguration(config) + FromDiskExporterImpl.builder(storage) .setDeserializer(SignalDeserializer.ofMetrics()) .setExportFunction(exporter::export) - .setDebugEnabled(config.isDebugEnabled()) .build(); return new MetricFromDiskExporter(delegate); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java index bf2e7066f..83d2fc73c 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java @@ -5,10 +5,9 @@ package io.opentelemetry.contrib.disk.buffering; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -31,16 +30,12 @@ public class MetricToDiskExporter implements MetricExporter { * Creates a new MetricToDiskExporter that will buffer Metric telemetry on disk storage. * * @param delegate - The MetricExporter to delegate to if disk writing fails. - * @param config - The StorageConfiguration that specifies how storage is managed. + * @param storage - The Storage instance that specifies how storage is managed. * @return A new MetricToDiskExporter instance. - * @throws IOException if the delegate ToDiskExporter could not be created. */ - public static MetricToDiskExporter create(MetricExporter delegate, StorageConfiguration config) - throws IOException { + public static MetricToDiskExporter create(MetricExporter delegate, Storage storage) { ToDiskExporter toDisk = - ToDiskExporter.builder() - .setFolderName(SignalTypes.metrics.name()) - .setStorageConfiguration(config) + ToDiskExporter.builder(storage) .setSerializer(SignalSerializer.ofMetrics()) .setExportFunction(delegate::export) .build(); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java index c23ac043e..e3c7992ba 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java @@ -5,11 +5,10 @@ package io.opentelemetry.contrib.disk.buffering; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; @@ -19,15 +18,12 @@ public class SpanFromDiskExporter implements FromDiskExporter { private final FromDiskExporterImpl delegate; - public static SpanFromDiskExporter create(SpanExporter exporter, StorageConfiguration config) + public static SpanFromDiskExporter create(SpanExporter exporter, Storage storage) throws IOException { FromDiskExporterImpl delegate = - FromDiskExporterImpl.builder() - .setFolderName(SignalTypes.spans.name()) - .setStorageConfiguration(config) + FromDiskExporterImpl.builder(storage) .setDeserializer(SignalDeserializer.ofSpans()) .setExportFunction(exporter::export) - .setDebugEnabled(config.isDebugEnabled()) .build(); return new SpanFromDiskExporter(delegate); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java index d64a4cd71..d5ca81518 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java @@ -5,10 +5,9 @@ package io.opentelemetry.contrib.disk.buffering; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -27,16 +26,12 @@ public class SpanToDiskExporter implements SpanExporter { * Creates a new SpanToDiskExporter that will buffer Span telemetry on disk storage. * * @param delegate - The SpanExporter to delegate to if disk writing fails. - * @param config - The StorageConfiguration that specifies how storage is managed. + * @param storage - The Storage instance that specifies how storage is managed. * @return A new SpanToDiskExporter instance. - * @throws IOException if the delegate ToDiskExporter could not be created. */ - public static SpanToDiskExporter create(SpanExporter delegate, StorageConfiguration config) - throws IOException { + public static SpanToDiskExporter create(SpanExporter delegate, Storage storage) { ToDiskExporter toDisk = - ToDiskExporter.builder() - .setFolderName(SignalTypes.spans.name()) - .setStorageConfiguration(config) + ToDiskExporter.builder(storage) .setSerializer(SignalSerializer.ofSpans()) .setExportFunction(delegate::export) .build(); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java index eec298469..a91ded1f3 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java @@ -8,11 +8,8 @@ import static java.util.Collections.emptyList; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.contrib.disk.buffering.internal.storage.StorageBuilder; -import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; import java.io.IOException; import java.util.Collection; @@ -22,36 +19,23 @@ public class FromDiskExporterBuilder { private SignalDeserializer serializer = noopDeserializer(); + private final Storage storage; + private Function, CompletableResultCode> exportFunction = x -> CompletableResultCode.ofFailure(); - private boolean debugEnabled = false; + public FromDiskExporterBuilder(Storage storage) { + if (storage == null) { + throw new NullPointerException("Storage cannot be null"); + } + this.storage = storage; + } @NotNull private static SignalDeserializer noopDeserializer() { return x -> emptyList(); } - private final StorageBuilder storageBuilder = Storage.builder(); - - @CanIgnoreReturnValue - public FromDiskExporterBuilder setFolderName(String folderName) { - storageBuilder.setFolderName(folderName); - return this; - } - - @CanIgnoreReturnValue - public FromDiskExporterBuilder setStorageConfiguration(StorageConfiguration configuration) { - storageBuilder.setStorageConfiguration(configuration); - return this; - } - - @CanIgnoreReturnValue - public FromDiskExporterBuilder setStorageClock(Clock clock) { - storageBuilder.setStorageClock(clock); - return this; - } - @CanIgnoreReturnValue public FromDiskExporterBuilder setDeserializer(SignalDeserializer serializer) { this.serializer = serializer; @@ -65,19 +49,7 @@ public FromDiskExporterBuilder setExportFunction( return this; } - @CanIgnoreReturnValue - public FromDiskExporterBuilder enableDebug() { - return setDebugEnabled(true); - } - - @CanIgnoreReturnValue - public FromDiskExporterBuilder setDebugEnabled(boolean debugEnabled) { - this.debugEnabled = debugEnabled; - return this; - } - public FromDiskExporterImpl build() throws IOException { - Storage storage = storageBuilder.build(); - return new FromDiskExporterImpl<>(serializer, exportFunction, storage, debugEnabled); + return new FromDiskExporterImpl<>(serializer, exportFunction, storage); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java index 19ef6fe2c..5ba5c2390 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java @@ -32,17 +32,17 @@ public final class FromDiskExporterImpl implements FromDiskExporter FromDiskExporterImpl( SignalDeserializer deserializer, Function, CompletableResultCode> exportFunction, - Storage storage, - boolean debugEnabled) { + Storage storage) { this.deserializer = deserializer; this.exportFunction = exportFunction; this.storage = storage; this.logger = - DebugLogger.wrap(Logger.getLogger(FromDiskExporterImpl.class.getName()), debugEnabled); + DebugLogger.wrap( + Logger.getLogger(FromDiskExporterImpl.class.getName()), storage.isDebugEnabled()); } - public static FromDiskExporterBuilder builder() { - return new FromDiskExporterBuilder<>(); + public static FromDiskExporterBuilder builder(Storage storage) { + return new FromDiskExporterBuilder<>(storage); } /** diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java index 1a43cb5eb..b54e3cc16 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java @@ -25,16 +25,17 @@ public class ToDiskExporter { ToDiskExporter( SignalSerializer serializer, Function, CompletableResultCode> exportFunction, - Storage storage, - boolean debugEnabled) { + Storage storage) { this.serializer = serializer; this.exportFunction = exportFunction; this.storage = storage; - this.logger = DebugLogger.wrap(Logger.getLogger(ToDiskExporter.class.getName()), debugEnabled); + this.logger = + DebugLogger.wrap( + Logger.getLogger(ToDiskExporter.class.getName()), storage.isDebugEnabled()); } - public static ToDiskExporterBuilder builder() { - return new ToDiskExporterBuilder<>(); + public static ToDiskExporterBuilder builder(Storage storage) { + return new ToDiskExporterBuilder<>(storage); } public CompletableResultCode export(Collection data) { diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java index 3ac7d2503..069e08986 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java @@ -6,13 +6,9 @@ package io.opentelemetry.contrib.disk.buffering.internal.exporter; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.contrib.disk.buffering.internal.storage.StorageBuilder; -import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; -import java.io.IOException; import java.util.Collection; import java.util.function.Function; @@ -20,42 +16,16 @@ public final class ToDiskExporterBuilder { private SignalSerializer serializer = ts -> new byte[0]; - private final StorageBuilder storageBuilder = Storage.builder(); + private final Storage storage; private Function, CompletableResultCode> exportFunction = x -> CompletableResultCode.ofFailure(); - private boolean debugEnabled = false; - ToDiskExporterBuilder() {} - - @CanIgnoreReturnValue - public ToDiskExporterBuilder enableDebug() { - return setDebugEnabled(true); - } - - @CanIgnoreReturnValue - public ToDiskExporterBuilder setDebugEnabled(boolean debugEnabled) { - this.debugEnabled = debugEnabled; - return this; - } - - @CanIgnoreReturnValue - public ToDiskExporterBuilder setFolderName(String folderName) { - storageBuilder.setFolderName(folderName); - return this; - } - - @CanIgnoreReturnValue - public ToDiskExporterBuilder setStorageConfiguration(StorageConfiguration configuration) { - validateConfiguration(configuration); - storageBuilder.setStorageConfiguration(configuration); - return this; - } - - @CanIgnoreReturnValue - public ToDiskExporterBuilder setStorageClock(Clock clock) { - storageBuilder.setStorageClock(clock); - return this; + ToDiskExporterBuilder(Storage storage) { + if (storage == null) { + throw new NullPointerException("Storage cannot be null"); + } + this.storage = storage; } @CanIgnoreReturnValue @@ -71,15 +41,7 @@ public ToDiskExporterBuilder setExportFunction( return this; } - public ToDiskExporter build() throws IOException { - Storage storage = storageBuilder.build(); - return new ToDiskExporter<>(serializer, exportFunction, storage, debugEnabled); - } - - private static void validateConfiguration(StorageConfiguration configuration) { - if (configuration.getMinFileAgeForReadMillis() <= configuration.getMaxFileAgeForWriteMillis()) { - throw new IllegalArgumentException( - "The configured max file age for writing must be lower than the configured min file age for reading"); - } + public ToDiskExporter build() { + return new ToDiskExporter<>(serializer, exportFunction, storage); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java index 9ff0f9410..51322b41e 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.models; import com.google.auto.value.AutoValue; -import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; @@ -51,17 +50,6 @@ public abstract static class Builder { public abstract Builder setSeverityText(String value); - @Deprecated - @CanIgnoreReturnValue - public Builder setBody(io.opentelemetry.sdk.logs.data.Body body) { - if (body.getType() == io.opentelemetry.sdk.logs.data.Body.Type.STRING) { - setBodyValue(Value.of(body.asString())); - } else if (body.getType() == io.opentelemetry.sdk.logs.data.Body.Type.EMPTY) { - setBodyValue(null); - } - return this; - } - public abstract Builder setBodyValue(@Nullable Value value); public abstract Builder setAttributes(Attributes value); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java index 4ff60cbdc..73a263490 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java @@ -14,6 +14,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; import io.opentelemetry.contrib.disk.buffering.internal.utils.DebugLogger; +import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import java.io.Closeable; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; @@ -24,8 +25,8 @@ public final class Storage implements Closeable { private static final int MAX_ATTEMPTS = 3; private final DebugLogger logger; - private final FolderManager folderManager; + private final boolean debugEnabled; private final AtomicBoolean isClosed = new AtomicBoolean(false); @Nullable private WritableFile writableFile; @Nullable private ReadableFile readableFile; @@ -34,10 +35,15 @@ public Storage(FolderManager folderManager, boolean debugEnabled) { this.folderManager = folderManager; this.logger = DebugLogger.wrap(Logger.getLogger(FromDiskExporterImpl.class.getName()), debugEnabled); + this.debugEnabled = debugEnabled; + } + + public static StorageBuilder builder(SignalTypes types) { + return new StorageBuilder(types); } - public static StorageBuilder builder() { - return new StorageBuilder(); + public boolean isDebugEnabled() { + return debugEnabled; } /** @@ -72,6 +78,14 @@ private boolean write(byte[] item, int attemptNumber) throws IOException { return true; } + public void flush() throws IOException { + if (writableFile != null) { + writableFile.flush(); + } else { + logger.log("No writable file to flush."); + } + } + /** * Attempts to read an item from a ready-to-read file. * diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java index c8b0435ca..d43bc18b2 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java @@ -7,6 +7,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; +import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.sdk.common.Clock; import java.io.File; import java.io.IOException; @@ -17,20 +18,17 @@ public class StorageBuilder { private static final Logger logger = Logger.getLogger(StorageBuilder.class.getName()); - private String folderName = "data"; + private final String folderName; private StorageConfiguration configuration = StorageConfiguration.getDefault(new File(".")); private Clock clock = Clock.getDefault(); - StorageBuilder() {} - - @CanIgnoreReturnValue - public StorageBuilder setFolderName(String folderName) { - this.folderName = folderName; - return this; + StorageBuilder(SignalTypes types) { + folderName = types.name(); } @CanIgnoreReturnValue public StorageBuilder setStorageConfiguration(StorageConfiguration configuration) { + validateConfiguration(configuration); this.configuration = configuration; return this; } @@ -57,4 +55,11 @@ private static File ensureSubdir(File rootDir, String child) throws IOException } throw new IOException("Could not create the subdir: '" + child + "' inside: " + rootDir); } + + private static void validateConfiguration(StorageConfiguration configuration) { + if (configuration.getMinFileAgeForReadMillis() <= configuration.getMaxFileAgeForWriteMillis()) { + throw new IllegalArgumentException( + "The configured max file age for writing must be lower than the configured min file age for reading"); + } + } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java index 519e9da66..6bf082ca5 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java @@ -94,4 +94,8 @@ public synchronized void close() throws IOException { public String toString() { return "WritableFile{" + "file=" + file + '}'; } + + public void flush() throws IOException { + out.flush(); + } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java index b2955630e..65c81b842 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java @@ -18,6 +18,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.DeserializationException; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.TestData; +import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.SpanData; @@ -40,7 +41,7 @@ class FromDiskExporterImplTest { private FromDiskExporterImpl exporter; private final List deserializedData = Collections.emptyList(); @TempDir File rootDir; - private static final String STORAGE_FOLDER_NAME = "testName"; + private static final String STORAGE_FOLDER_NAME = SignalTypes.spans.name(); @BeforeEach void setUp() throws IOException { @@ -49,12 +50,10 @@ void setUp() throws IOException { setUpSerializer(); wrapped = mock(); exporter = - FromDiskExporterImpl.builder() - .setFolderName(STORAGE_FOLDER_NAME) - .setStorageConfiguration(TestData.getDefaultConfiguration(rootDir)) + FromDiskExporterImpl.builder( + TestData.getDefaultStorage(rootDir, SignalTypes.spans, clock)) .setDeserializer(deserializer) .setExportFunction(wrapped::export) - .setStorageClock(clock) .build(); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java index c1c42b0bb..b2da05c24 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java @@ -22,6 +22,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; @@ -53,25 +54,28 @@ public class IntegrationTest { private InMemorySpanExporter memorySpanExporter; - private SpanToDiskExporter spanToDiskExporter; private Tracer tracer; private InMemoryMetricExporter memoryMetricExporter; - private MetricToDiskExporter metricToDiskExporter; private SdkMeterProvider meterProvider; private Meter meter; private InMemoryLogRecordExporter memoryLogRecordExporter; - private LogRecordToDiskExporter logToDiskExporter; private Logger logger; private Clock clock; @TempDir File rootDir; private static final long INITIAL_TIME_IN_MILLIS = 1000; private static final long NOW_NANOS = MILLISECONDS.toNanos(INITIAL_TIME_IN_MILLIS); private StorageConfiguration storageConfig; + private Storage spanStorage; @BeforeEach void setUp() throws IOException { - storageConfig = StorageConfiguration.getDefault(rootDir); clock = mock(); + storageConfig = StorageConfiguration.getDefault(rootDir); + spanStorage = + Storage.builder(SignalTypes.spans) + .setStorageConfiguration(storageConfig) + .setStorageClock(clock) + .build(); when(clock.now()).thenReturn(NOW_NANOS); @@ -79,14 +83,15 @@ void setUp() throws IOException { memorySpanExporter = InMemorySpanExporter.create(); ToDiskExporter toDiskSpanExporter = buildToDiskExporter(SignalSerializer.ofSpans(), memorySpanExporter::export); - spanToDiskExporter = new SpanToDiskExporter(toDiskSpanExporter); + SpanToDiskExporter spanToDiskExporter = new SpanToDiskExporter(toDiskSpanExporter); tracer = createTracerProvider(spanToDiskExporter).get("SpanInstrumentationScope"); // Setting up metrics memoryMetricExporter = InMemoryMetricExporter.create(); ToDiskExporter toDiskMetricExporter = buildToDiskExporter(SignalSerializer.ofMetrics(), memoryMetricExporter::export); - metricToDiskExporter = new MetricToDiskExporter(toDiskMetricExporter, memoryMetricExporter); + MetricToDiskExporter metricToDiskExporter = + new MetricToDiskExporter(toDiskMetricExporter, memoryMetricExporter); meterProvider = createMeterProvider(metricToDiskExporter); meter = meterProvider.get("MetricInstrumentationScope"); @@ -94,36 +99,26 @@ void setUp() throws IOException { memoryLogRecordExporter = InMemoryLogRecordExporter.create(); ToDiskExporter toDiskLogExporter = buildToDiskExporter(SignalSerializer.ofLogs(), memoryLogRecordExporter::export); - logToDiskExporter = new LogRecordToDiskExporter(toDiskLogExporter); + LogRecordToDiskExporter logToDiskExporter = new LogRecordToDiskExporter(toDiskLogExporter); logger = createLoggerProvider(logToDiskExporter).get("LogInstrumentationScope"); } @NotNull private ToDiskExporter buildToDiskExporter( - SignalSerializer serializer, Function, CompletableResultCode> exporter) - throws IOException { - return ToDiskExporter.builder() - .setFolderName(SignalTypes.spans.name()) - .setStorageConfiguration(storageConfig) + SignalSerializer serializer, Function, CompletableResultCode> exporter) { + return ToDiskExporter.builder(spanStorage) .setSerializer(serializer) .setExportFunction(exporter) - .setStorageClock(clock) .build(); } @NotNull - private FromDiskExporterImpl buildFromDiskExporter( + private static FromDiskExporterImpl buildFromDiskExporter( FromDiskExporterBuilder builder, Function, CompletableResultCode> exportFunction, SignalDeserializer deserializer) throws IOException { - return builder - .setExportFunction(exportFunction) - .setFolderName(SignalTypes.spans.name()) - .setStorageConfiguration(storageConfig) - .setDeserializer(deserializer) - .setStorageClock(clock) - .build(); + return builder.setExportFunction(exportFunction).setDeserializer(deserializer).build(); } @Test @@ -132,7 +127,7 @@ void verifySpansIntegration() throws IOException { span.end(); FromDiskExporterImpl fromDiskExporter = buildFromDiskExporter( - FromDiskExporterImpl.builder(), + FromDiskExporterImpl.builder(spanStorage), memorySpanExporter::export, SignalDeserializer.ofSpans()); assertExporter(fromDiskExporter, () -> memorySpanExporter.getFinishedSpanItems().size()); @@ -145,7 +140,7 @@ void verifyMetricsIntegration() throws IOException { FromDiskExporterImpl fromDiskExporter = buildFromDiskExporter( - FromDiskExporterImpl.builder(), + FromDiskExporterImpl.builder(spanStorage), memoryMetricExporter::export, SignalDeserializer.ofMetrics()); assertExporter(fromDiskExporter, () -> memoryMetricExporter.getFinishedMetricItems().size()); @@ -157,7 +152,7 @@ void verifyLogRecordsIntegration() throws IOException { FromDiskExporterImpl fromDiskExporter = buildFromDiskExporter( - FromDiskExporterImpl.builder(), + FromDiskExporterImpl.builder(spanStorage), memoryLogRecordExporter::export, SignalDeserializer.ofLogs()); assertExporter( diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java index 05eef5a03..ae503ecdf 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -20,12 +19,12 @@ import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.internal.files.DefaultTemporaryFileProvider; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.data.SpanData; @@ -48,22 +47,24 @@ class SpanFromDiskExporterTest { @SuppressWarnings("unchecked") @Test void fromDisk() throws Exception { - StorageConfiguration config = - StorageConfiguration.builder() - .setRootDir(tempDir) - .setMaxFileAgeForWriteMillis(TimeUnit.HOURS.toMillis(24)) - .setMinFileAgeForReadMillis(0) - .setMaxFileAgeForReadMillis(TimeUnit.HOURS.toMillis(24)) - .setTemporaryFileProvider(DefaultTemporaryFileProvider.getInstance()) + Clock clock = mock(Clock.class); + long start = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); + when(clock.now()).thenReturn(start); + Storage storage = + Storage.builder(SignalTypes.spans) + .setStorageConfiguration(StorageConfiguration.builder().setRootDir(tempDir).build()) + .setStorageClock(clock) .build(); - List spans = writeSomeSpans(config); + List spans = writeSomeSpans(storage); + + when(clock.now()).thenReturn(start + TimeUnit.SECONDS.toNanos(60)); SpanExporter exporter = mock(); ArgumentCaptor> capture = ArgumentCaptor.forClass(Collection.class); when(exporter.export(capture.capture())).thenReturn(CompletableResultCode.ofSuccess()); - SpanFromDiskExporter testClass = SpanFromDiskExporter.create(exporter, config); + SpanFromDiskExporter testClass = SpanFromDiskExporter.create(exporter, storage); boolean result = testClass.exportStoredBatch(30, TimeUnit.SECONDS); assertThat(result).isTrue(); List exportedSpans = (List) capture.getValue(); @@ -79,23 +80,14 @@ void fromDisk() throws Exception { verify(exporter).export(eq(Arrays.asList(expected1, expected2))); } - private static List writeSomeSpans(StorageConfiguration config) throws Exception { + private static List writeSomeSpans(Storage storage) throws Exception { long now = System.currentTimeMillis() * 1_000_000; SpanData span1 = makeSpan1(TraceFlags.getDefault(), now); SpanData span2 = makeSpan2(TraceFlags.getSampled(), now); List spans = Arrays.asList(span1, span2); - SignalSerializer serializer = SignalSerializer.ofSpans(); - File subdir = new File(config.getRootDir(), SignalTypes.spans.name()); - assertTrue(subdir.mkdir()); - - Storage storage = - Storage.builder() - .setStorageConfiguration(config) - .setFolderName(SignalTypes.spans.name()) - .build(); - storage.write(serializer.serialize(spans)); - storage.close(); + storage.write(SignalSerializer.ofSpans().serialize(spans)); + storage.flush(); return spans; } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilderTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilderTest.java deleted file mode 100644 index 288388e03..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilderTest.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.sdk.trace.data.SpanData; -import java.io.File; -import org.junit.jupiter.api.Test; - -class ToDiskExporterBuilderTest { - - @Test - void whenMinFileReadIsNotGraterThanMaxFileWrite_throwException() { - StorageConfiguration invalidConfig = - StorageConfiguration.builder() - .setMaxFileAgeForWriteMillis(2) - .setMinFileAgeForReadMillis(1) - .setRootDir(new File(".")) - .build(); - - assertThatThrownBy( - () -> ToDiskExporter.builder().setStorageConfiguration(invalidConfig)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "The configured max file age for writing must be lower than the configured min file age for reading"); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java index 865aa6298..f7b6e3ff6 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java @@ -49,7 +49,7 @@ void setup() { exportedFnSeen = x; return exportFnResultToReturn.get(); }; - toDiskExporter = new ToDiskExporter<>(serializer, exportFn, storage, true); + toDiskExporter = new ToDiskExporter<>(serializer, exportFn, storage); when(serializer.serialize(records)).thenReturn(serialized); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java index d59c3464f..9accaefff 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java @@ -6,6 +6,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage; import static io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult.TRY_LATER; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.any; @@ -15,11 +16,14 @@ import static org.mockito.Mockito.verifyNoInteractions; import static org.mockito.Mockito.when; +import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; +import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import java.io.File; import java.io.IOException; import java.util.function.Function; import org.junit.jupiter.api.BeforeEach; @@ -222,6 +226,22 @@ void whenClosing_closeWriterAndReaderIfNotNull() throws IOException { verify(readableFile).close(); } + @Test + void whenMinFileReadIsNotGraterThanMaxFileWrite_throwException() { + StorageConfiguration invalidConfig = + StorageConfiguration.builder() + .setMaxFileAgeForWriteMillis(2) + .setMinFileAgeForReadMillis(1) + .setRootDir(new File(".")) + .build(); + + assertThatThrownBy( + () -> Storage.builder(SignalTypes.logs).setStorageConfiguration(invalidConfig)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage( + "The configured max file age for writing must be lower than the configured min file age for reading"); + } + private static WritableFile createWritableFile() throws IOException { WritableFile mock = mock(); when(mock.append(any())).thenReturn(WritableResult.SUCCEEDED); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java index a9a2003ae..8e66dde04 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java @@ -8,7 +8,10 @@ import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.config.TemporaryFileProvider; import io.opentelemetry.contrib.disk.buffering.internal.files.DefaultTemporaryFileProvider; +import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.sdk.common.Clock; import java.io.File; +import java.io.IOException; public final class TestData { @@ -23,6 +26,15 @@ public static StorageConfiguration getDefaultConfiguration(File rootDir) { return getConfiguration(fileProvider, rootDir); } + public static Storage getDefaultStorage(File rootDir, SignalTypes types, Clock clock) + throws IOException { + TemporaryFileProvider fileProvider = DefaultTemporaryFileProvider.getInstance(); + return Storage.builder(types) + .setStorageConfiguration(getConfiguration(fileProvider, rootDir)) + .setStorageClock(clock) + .build(); + } + public static StorageConfiguration getConfiguration( TemporaryFileProvider fileProvider, File rootDir) { return StorageConfiguration.builder() From f4ff5f4b02df0b1572233ca9bfdd124489cd3b53 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 29 May 2025 11:17:03 -0700 Subject: [PATCH 053/371] Simplify auto spotless workflow (#1919) --- .github/workflows/auto-spotless-apply.yml | 40 ++++++++++------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 6312417f7..13753e8c3 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -42,31 +42,10 @@ jobs: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 if: steps.unzip-patch.outputs.exists == 'true' with: + repository: "${{ github.event.workflow_run.head_repository.full_name }}" + ref: "${{ github.event.workflow_run.head_branch }}" token: ${{ steps.otelbot-token.outputs.token }} - - id: get-pr - if: steps.unzip-patch.outputs.exists == 'true' - name: Get PR - env: - PR_BRANCH: |- - ${{ - (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) - && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) - || github.event.workflow_run.head_branch - }} - GH_TOKEN: ${{ github.token }} - run: | - echo gh pr view "${PR_BRANCH}" --json number --jq .number - number=$(gh pr view "${PR_BRANCH}" --json number --jq .number) - echo $number - echo "number=$number" >> $GITHUB_OUTPUT - - - name: Check out PR branch - if: steps.unzip-patch.outputs.exists == 'true' - env: - GH_TOKEN: ${{ github.token }} - run: gh pr checkout ${{ steps.get-pr.outputs.number }} - - name: Use CLA approved github bot if: steps.unzip-patch.outputs.exists == 'true' # IMPORTANT do not call the .github/scripts/use-cla-approved-bot.sh @@ -82,6 +61,21 @@ jobs: git commit -a -m "./gradlew spotlessApply" git push + - id: get-pr + if: steps.unzip-patch.outputs.exists == 'true' + name: Get PR + env: + PR_BRANCH: |- + ${{ + (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) + && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) + || github.event.workflow_run.head_branch + }} + GH_TOKEN: ${{ github.token }} + run: | + number=$(gh pr view --repo "${{ github.event.workflow_run.repository.full_name }}" "${PR_BRANCH}" --json number --jq .number) + echo "number=$number" >> $GITHUB_OUTPUT + - if: steps.unzip-patch.outputs.exists == 'true' && success() env: GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} From 793a3ab3aaaf3ad09fa14e5f63a50c989b82d430 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 30 May 2025 09:17:56 -0700 Subject: [PATCH 054/371] Fix spotless automation (#1922) --- .github/workflows/auto-spotless-apply.yml | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 13753e8c3..47379ec8a 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -65,25 +65,30 @@ jobs: if: steps.unzip-patch.outputs.exists == 'true' name: Get PR env: + GH_REPO: ${{ github.repository }} + GH_TOKEN: ${{ github.token }} PR_BRANCH: |- ${{ (github.event.workflow_run.head_repository.owner.login != github.event.workflow_run.repository.owner.login) && format('{0}:{1}', github.event.workflow_run.head_repository.owner.login, github.event.workflow_run.head_branch) || github.event.workflow_run.head_branch }} - GH_TOKEN: ${{ github.token }} run: | - number=$(gh pr view --repo "${{ github.event.workflow_run.repository.full_name }}" "${PR_BRANCH}" --json number --jq .number) + number=$(gh pr view "$PR_BRANCH" --json number --jq .number) echo "number=$number" >> $GITHUB_OUTPUT - if: steps.unzip-patch.outputs.exists == 'true' && success() env: + GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + PR_NUMBER: ${{ steps.get-pr.outputs.number }} run: | - gh pr comment ${{ steps.get-pr.outputs.number }} --body "🔧 The result from spotlessApply was committed to the PR branch." + gh pr comment $PR_NUMBER --body "🔧 The result from spotlessApply was committed to the PR branch." - if: steps.unzip-patch.outputs.exists == 'true' && failure() env: + GH_REPO: ${{ github.repository }} GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + PR_NUMBER: ${{ steps.get-pr.outputs.number }} run: | - gh pr comment ${{ steps.get-pr.outputs.number }} --body "❌ The result from spotlessApply could not be committed to the PR branch, see logs: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID." + gh pr comment $PR_NUMBER --body "❌ The result from spotlessApply could not be committed to the PR branch, see logs: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID." From 4e7590ed5b2f028ff84e5ad965936a049dcb04b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 09:18:03 -0700 Subject: [PATCH 055/371] fix(deps): update all patch versions to v1.21.1 (#1926) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- dependencyManagement/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index a337dff27..dc0e6fecf 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -136,7 +136,7 @@ testing { implementation(project(project.path)) implementation(enforcedPlatform("org.junit:junit-bom:5.12.2")) - implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.0")) + implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.1")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 936485781..78e2b4579 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { api("org.junit-pioneer:junit-pioneer:1.9.1") api("org.skyscreamer:jsonassert:1.5.3") api("org.apache.kafka:kafka-clients:4.0.0") - api("org.testcontainers:kafka:1.21.0") + api("org.testcontainers:kafka:1.21.1") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") api("tools.profiler:async-profiler:4.0") From 2a7610aad1c2eab56a90e1024179bc418d705610 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 30 May 2025 10:26:56 -0700 Subject: [PATCH 056/371] Setting up: A dummy PR to give otelbot-java-contrib one merged PR under its belt (#1925) --- .github/workflows/create-dummy-pr.yml | 42 +++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 .github/workflows/create-dummy-pr.yml diff --git a/.github/workflows/create-dummy-pr.yml b/.github/workflows/create-dummy-pr.yml new file mode 100644 index 000000000..3ff83229d --- /dev/null +++ b/.github/workflows/create-dummy-pr.yml @@ -0,0 +1,42 @@ +name: Create a dummy PR + +on: + workflow_dispatch: + +permissions: + contents: read + +jobs: + create: + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Need to make some kind of change, and this workflow will no longer be needed after running it the one time, so... + run: git rm .github/workflows/create-dummy-pr.yml + + - name: Use CLA approved bot + run: .github/scripts/use-cla-approved-bot.sh + + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + id: otelbot-token + with: + app-id: 1296620 + private-key: ${{ secrets.OTELBOT_JAVA_CONTRIB_PRIVATE_KEY }} + + - name: Create pull request against the release branch + env: + GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} + run: | + message="A dummy PR to give otelbot-java-contrib one merged PR under its belt" + branch="otelbot/dummy-pr" + + git checkout -b $branch + git commit -a -m "$message" + git push --set-upstream origin $branch + gh pr create --title "$message" \ + --body "$message." \ + --base main From 124993ee049a46580570a0725ce7def3c476e295 Mon Sep 17 00:00:00 2001 From: "otelbot-java-contrib[bot]" <212438579+otelbot-java-contrib[bot]@users.noreply.github.com> Date: Fri, 30 May 2025 17:31:27 +0000 Subject: [PATCH 057/371] A dummy PR to give otelbot-java-contrib one merged PR under its belt (#1929) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/workflows/create-dummy-pr.yml | 42 --------------------------- 1 file changed, 42 deletions(-) delete mode 100644 .github/workflows/create-dummy-pr.yml diff --git a/.github/workflows/create-dummy-pr.yml b/.github/workflows/create-dummy-pr.yml deleted file mode 100644 index 3ff83229d..000000000 --- a/.github/workflows/create-dummy-pr.yml +++ /dev/null @@ -1,42 +0,0 @@ -name: Create a dummy PR - -on: - workflow_dispatch: - -permissions: - contents: read - -jobs: - create: - permissions: - contents: write - pull-requests: write - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - name: Need to make some kind of change, and this workflow will no longer be needed after running it the one time, so... - run: git rm .github/workflows/create-dummy-pr.yml - - - name: Use CLA approved bot - run: .github/scripts/use-cla-approved-bot.sh - - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 - id: otelbot-token - with: - app-id: 1296620 - private-key: ${{ secrets.OTELBOT_JAVA_CONTRIB_PRIVATE_KEY }} - - - name: Create pull request against the release branch - env: - GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} - run: | - message="A dummy PR to give otelbot-java-contrib one merged PR under its belt" - branch="otelbot/dummy-pr" - - git checkout -b $branch - git commit -a -m "$message" - git push --set-upstream origin $branch - gh pr create --title "$message" \ - --body "$message." \ - --base main From 8d9b238a14c482e06b275c859ac3abf14616931c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:54:35 +0300 Subject: [PATCH 058/371] chore(deps): update ossf/scorecard-action action to v2.4.2 (#1930) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index d8503ba1e..839b64885 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: ossf/scorecard-action@f49aabe0b5af0936a0987cfb85d86b75731b0186 # v2.4.1 + - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: results_file: results.sarif results_format: sarif From 805a784874b33ccc53d1c86253adbcc04ba65f4c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:54:55 +0300 Subject: [PATCH 059/371] fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.36.0 (#1931) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 94902ed9b..8f4a9d90c 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.35.0") + implementation("com.google.auth:google-auth-library-oauth2-http:1.36.0") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From 23a2a7046a0ea140ad7583def73180cabc001833 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:55:17 +0300 Subject: [PATCH 060/371] fix(deps): update dependency com.google.cloud.opentelemetry:detector-resources-support to v0.35.0 (#1932) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-resources/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-resources/build.gradle.kts b/gcp-resources/build.gradle.kts index 2808f9ae2..2a88c60b5 100644 --- a/gcp-resources/build.gradle.kts +++ b/gcp-resources/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { api("io.opentelemetry:opentelemetry-sdk") // Provides GCP resource detection support - implementation("com.google.cloud.opentelemetry:detector-resources-support:0.34.0") + implementation("com.google.cloud.opentelemetry:detector-resources-support:0.35.0") testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") From 3be75ed83ea00ef0e743a3cf61dc65197d0cd8d9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 2 Jun 2025 15:55:41 +0300 Subject: [PATCH 061/371] fix(deps): update dependency org.junit:junit-bom to v5.13.0 (#1933) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index dc0e6fecf..05412a214 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -135,7 +135,7 @@ testing { dependencies { implementation(project(project.path)) - implementation(enforcedPlatform("org.junit:junit-bom:5.12.2")) + implementation(enforcedPlatform("org.junit:junit-bom:5.13.0")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.1")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) From a3deeb2e86744e46c77590fadda97a74223e0516 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 4 Jun 2025 04:21:02 +0200 Subject: [PATCH 062/371] Disk buffering reading refactor (#1917) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- disk-buffering/README.md | 6 -- disk-buffering/build.gradle.kts | 8 +- .../config/StorageConfiguration.java | 10 +-- .../config/TemporaryFileProvider.java | 20 ----- .../files/DefaultTemporaryFileProvider.java | 25 ------ .../storage/files/FileOperations.java | 6 -- .../internal/storage/files/ReadableFile.java | 65 ++++----------- .../internal/storage/files/WritableFile.java | 1 - .../reader/DelimitedProtoStreamReader.java | 22 ++--- .../storage/files/reader/ReadResult.java | 9 +- .../storage/files/reader/StreamReader.java | 17 +--- .../files/utils/CountingInputStream.java | 68 --------------- .../storage/files/utils/FileStream.java | 82 +++++++++++++++++++ .../storage/files/utils/FileTransferUtil.java | 35 -------- .../buffering/FromDiskExporterImplTest.java | 2 +- .../internal/storage/FolderManagerTest.java | 4 +- .../buffering/internal/storage/TestData.java | 16 +--- .../storage/files/ReadableFileTest.java | 43 +++++----- .../storage/files/WritableFileTest.java | 2 +- .../storage/files/utils/FileStreamTest.java | 82 +++++++++++++++++++ .../testutils/BaseSignalSerializerTest.java | 2 +- 21 files changed, 227 insertions(+), 298 deletions(-) delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/TemporaryFileProvider.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/files/DefaultTemporaryFileProvider.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/CountingInputStream.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStream.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileTransferUtil.java create mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java diff --git a/disk-buffering/README.md b/disk-buffering/README.md index 67dbb1f52..72e17144d 100644 --- a/disk-buffering/README.md +++ b/disk-buffering/README.md @@ -28,12 +28,6 @@ The configurable parameters are provided **per exporter**, the available ones ar * Max age for file reading, defaults to 18 hours. After that time passes, the file will be considered stale and will be removed when new files are created. No more data will be read from a file past this time. -* An instance - of [TemporaryFileProvider](src/main/java/io/opentelemetry/contrib/disk/buffering/config/TemporaryFileProvider.java), - defaults to calling `File.createTempFile`. This provider will be used when reading from the disk - in order create a temporary file from which each line (batch of signals) will be read and - sequentially get removed from the original cache file right after the data has been successfully - exported. ## Usage diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index ef86e9043..09f424aee 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -13,11 +13,6 @@ plugins { description = "Exporter implementations that store signals on disk" otelJava.moduleName.set("io.opentelemetry.contrib.exporters.disk") -java { - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - val protos by configurations.creating dependencies { @@ -75,7 +70,8 @@ wire { tasks.named("shadowJar") { archiveClassifier.set("") - configurations = emptyList() // To avoid embedding any dependencies as we only need to rename some local packages. + configurations = + emptyList() // To avoid embedding any dependencies as we only need to rename some local packages. relocate("io.opentelemetry.proto", "io.opentelemetry.diskbuffering.proto") mustRunAfter("jar") } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java index 2470b263a..4853ee72f 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.config; import com.google.auto.value.AutoValue; -import io.opentelemetry.contrib.disk.buffering.internal.files.DefaultTemporaryFileProvider; import java.io.File; import java.util.concurrent.TimeUnit; @@ -49,23 +48,18 @@ public abstract class StorageConfiguration { */ public abstract int getMaxFolderSize(); - /** A creator of temporary files needed to do the disk reading process. */ - public abstract TemporaryFileProvider getTemporaryFileProvider(); - public static StorageConfiguration getDefault(File rootDir) { return builder().setRootDir(rootDir).build(); } public static Builder builder() { - TemporaryFileProvider fileProvider = DefaultTemporaryFileProvider.getInstance(); return new AutoValue_StorageConfiguration.Builder() .setMaxFileSize(1024 * 1024) // 1MB .setMaxFolderSize(10 * 1024 * 1024) // 10MB .setMaxFileAgeForWriteMillis(TimeUnit.SECONDS.toMillis(30)) .setMinFileAgeForReadMillis(TimeUnit.SECONDS.toMillis(33)) .setMaxFileAgeForReadMillis(TimeUnit.HOURS.toMillis(18)) - .setDebugEnabled(false) - .setTemporaryFileProvider(fileProvider); + .setDebugEnabled(false); } @AutoValue.Builder @@ -80,8 +74,6 @@ public abstract static class Builder { public abstract Builder setMaxFolderSize(int value); - public abstract Builder setTemporaryFileProvider(TemporaryFileProvider value); - public abstract Builder setRootDir(File rootDir); public abstract Builder setDebugEnabled(boolean debugEnabled); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/TemporaryFileProvider.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/TemporaryFileProvider.java deleted file mode 100644 index 3cf803f9f..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/TemporaryFileProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.config; - -import java.io.File; -import java.io.IOException; - -/** Provides a temporary file needed to do the disk reading process. */ -public interface TemporaryFileProvider { - - /** - * Creates a temporary file. - * - * @param prefix The prefix for the provided file name. - */ - File createTemporaryFile(String prefix) throws IOException; -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/files/DefaultTemporaryFileProvider.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/files/DefaultTemporaryFileProvider.java deleted file mode 100644 index 9a9dfb8e6..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/files/DefaultTemporaryFileProvider.java +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.files; - -import io.opentelemetry.contrib.disk.buffering.config.TemporaryFileProvider; -import java.io.File; -import java.io.IOException; - -public final class DefaultTemporaryFileProvider implements TemporaryFileProvider { - private static final TemporaryFileProvider INSTANCE = new DefaultTemporaryFileProvider(); - - public static TemporaryFileProvider getInstance() { - return INSTANCE; - } - - private DefaultTemporaryFileProvider() {} - - @Override - public File createTemporaryFile(String prefix) throws IOException { - return File.createTempFile(prefix + "_", ".tmp"); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/FileOperations.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/FileOperations.java index 21544a991..316f51157 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/FileOperations.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/FileOperations.java @@ -9,15 +9,9 @@ import java.io.File; public interface FileOperations extends Closeable { - long getSize(); - boolean hasExpired(); boolean isClosed(); File getFile(); - - default String getFileName() { - return getFile().getName(); - } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java index f7383d60f..710e192bb 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java @@ -12,15 +12,11 @@ import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ReadResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.StreamReader; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils.FileTransferUtil; +import io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils.FileStream; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; import io.opentelemetry.sdk.common.Clock; import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import javax.annotation.Nullable; @@ -37,14 +33,11 @@ */ public final class ReadableFile implements FileOperations { @NotNull private final File file; - private final int originalFileSize; + private final FileStream fileStream; private final StreamReader reader; - private final FileTransferUtil fileTransferUtil; - private final File temporaryFile; private final Clock clock; private final long expireTimeMillis; private final AtomicBoolean isClosed = new AtomicBoolean(false); - private int readBytes = 0; @Nullable private ReadResult unconsumedResult; public ReadableFile( @@ -59,7 +52,7 @@ public ReadableFile( } public ReadableFile( - File file, + @NotNull File file, long createdTimeMillis, Clock clock, StorageConfiguration configuration, @@ -68,12 +61,8 @@ public ReadableFile( this.file = file; this.clock = clock; expireTimeMillis = createdTimeMillis + configuration.getMaxFileAgeForReadMillis(); - originalFileSize = (int) file.length(); - temporaryFile = configuration.getTemporaryFileProvider().createTemporaryFile(file.getName()); - copyFile(file, temporaryFile); - FileInputStream tempInputStream = new FileInputStream(temporaryFile); - fileTransferUtil = new FileTransferUtil(tempInputStream, file); - reader = readerFactory.create(tempInputStream); + fileStream = FileStream.create(file); + reader = readerFactory.create(fileStream); } /** @@ -101,11 +90,8 @@ public synchronized ReadableResult readAndProcess(Function 0) { - fileTransferUtil.transferBytes(readBytes, amountOfBytesToTransfer); - } else { + fileStream.truncateTop(); + if (fileStream.size() == 0) { cleanUp(); } return ReadableResult.SUCCEEDED; @@ -124,17 +110,7 @@ private ReadResult readNextItem() throws IOException { if (unconsumedResult != null) { return unconsumedResult; } - return reader.read(); - } - - private void cleanUp() throws IOException { - file.delete(); - close(); - } - - @Override - public long getSize() { - return originalFileSize; + return reader.readNext(); } @Override @@ -153,29 +129,18 @@ public File getFile() { return file; } + private void cleanUp() throws IOException { + close(); + if (!file.delete()) { + throw new IOException("Could not delete file: " + file); + } + } + @Override public synchronized void close() throws IOException { if (isClosed.compareAndSet(false, true)) { unconsumedResult = null; - fileTransferUtil.close(); reader.close(); - temporaryFile.delete(); - } - } - - /** - * This is needed instead of using Files.copy in order to keep it compatible with Android api < - * 26. - */ - private static void copyFile(File from, File to) throws IOException { - try (InputStream in = new FileInputStream(from); - OutputStream out = new FileOutputStream(to)) { - - byte[] buffer = new byte[1024]; - int lengthRead; - while ((lengthRead = in.read(buffer)) > 0) { - out.write(buffer, 0, lengthRead); - } } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java index 6bf082ca5..0f3d1d475 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java @@ -63,7 +63,6 @@ public synchronized WritableResult append(byte[] data) throws IOException { return WritableResult.SUCCEEDED; } - @Override public synchronized long getSize() { return size; } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java index 0f9723c4c..60a8e4f45 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java @@ -5,24 +5,21 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils.CountingInputStream; import io.opentelemetry.contrib.disk.buffering.internal.utils.ProtobufTools; import java.io.IOException; import java.io.InputStream; import javax.annotation.Nullable; -public final class DelimitedProtoStreamReader extends StreamReader { - private final CountingInputStream countingInputStream; +public final class DelimitedProtoStreamReader implements StreamReader { + private final InputStream inputStream; public DelimitedProtoStreamReader(InputStream inputStream) { - super(new CountingInputStream(inputStream)); - countingInputStream = (CountingInputStream) this.inputStream; + this.inputStream = inputStream; } @Override @Nullable - public ReadResult read() throws IOException { - int startingPosition = countingInputStream.getPosition(); + public ReadResult readNext() throws IOException { int itemSize = getNextItemSize(); if (itemSize < 1) { return null; @@ -31,7 +28,7 @@ public ReadResult read() throws IOException { if (inputStream.read(bytes) < 0) { return null; } - return new ReadResult(bytes, countingInputStream.getPosition() - startingPosition); + return new ReadResult(bytes); } private int getNextItemSize() { @@ -46,6 +43,11 @@ private int getNextItemSize() { } } + @Override + public void close() throws IOException { + inputStream.close(); + } + public static class Factory implements StreamReader.Factory { private static final Factory INSTANCE = new DelimitedProtoStreamReader.Factory(); @@ -57,8 +59,8 @@ public static Factory getInstance() { private Factory() {} @Override - public StreamReader create(InputStream stream) { - return new DelimitedProtoStreamReader(stream); + public StreamReader create(InputStream inputStream) { + return new DelimitedProtoStreamReader(inputStream); } } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java index 079c2396c..a9f5d1116 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java @@ -9,14 +9,7 @@ public final class ReadResult { /** The consumable data. */ public final byte[] content; - /** - * The total amount of data read from the stream. This number can be greater than the content - * length as it also takes into account any delimiters size. - */ - public final int totalReadLength; - - public ReadResult(byte[] content, int totalReadLength) { + public ReadResult(byte[] content) { this.content = content; - this.totalReadLength = totalReadLength; } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java index d263aad71..447315f1e 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java @@ -10,22 +10,11 @@ import java.io.InputStream; import javax.annotation.Nullable; -public abstract class StreamReader implements Closeable { - protected final InputStream inputStream; - - protected StreamReader(InputStream inputStream) { - this.inputStream = inputStream; - } - +public interface StreamReader extends Closeable { @Nullable - public abstract ReadResult read() throws IOException; - - @Override - public void close() throws IOException { - inputStream.close(); - } + ReadResult readNext() throws IOException; - public interface Factory { + interface Factory { StreamReader create(InputStream stream); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/CountingInputStream.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/CountingInputStream.java deleted file mode 100644 index 9faa2c018..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/CountingInputStream.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils; - -import java.io.FilterInputStream; -import java.io.IOException; -import java.io.InputStream; - -public final class CountingInputStream extends FilterInputStream { - - private int position; - private int mark = -1; - - public CountingInputStream(InputStream in) { - super(in); - } - - public int getPosition() { - return position; - } - - @Override - public synchronized void mark(int readlimit) { - in.mark(readlimit); - mark = position; - } - - @Override - public long skip(long n) throws IOException { - long result = in.skip(n); - position = (int) (position + result); - return result; - } - - @Override - public int read() throws IOException { - int result = in.read(); - if (result != -1) { - position++; - } - return result; - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - int result = in.read(b, off, len); - if (result != -1) { - position += result; - } - return result; - } - - @Override - public synchronized void reset() throws IOException { - if (!in.markSupported()) { - throw new IOException("Mark is not supported"); - } - if (mark == -1) { - throw new IOException("Mark is not set"); - } - - in.reset(); - position = mark; - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStream.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStream.java new file mode 100644 index 000000000..c49570922 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStream.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; +import org.jetbrains.annotations.NotNull; + +public class FileStream extends InputStream { + private final RandomAccessFile file; + private final FileChannel channel; + + public static FileStream create(File file) throws IOException { + RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rwd"); + FileChannel channel = randomAccessFile.getChannel(); + channel.force(false); + return new FileStream(randomAccessFile, channel); + } + + private FileStream(RandomAccessFile file, FileChannel channel) { + this.file = file; + this.channel = channel; + } + + @Override + public int read() throws IOException { + return file.read(); + } + + @Override + public int read(@NotNull byte[] bytes) throws IOException { + return file.read(bytes); + } + + @Override + public int read(@NotNull byte[] b, int off, int len) throws IOException { + return file.read(b, off, len); + } + + public long size() throws IOException { + return channel.size(); + } + + @Override + public void close() throws IOException { + channel.close(); + file.close(); + } + + public void truncateTop(long size) throws IOException { + file.seek(Math.min(size(), size)); + truncateTop(); + } + + public void truncateTop() throws IOException { + long position = file.getFilePointer(); + if (position == 0) { + return; + } + long remainingSize = size() - position; + if (remainingSize > 0) { + byte[] remainingBytes = new byte[(int) remainingSize]; + file.read(remainingBytes); + file.seek(0); + channel.truncate(remainingSize); + file.write(remainingBytes); + file.seek(0); + } else { + channel.truncate(0); + } + } + + public long getPosition() throws IOException { + return file.getFilePointer(); + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileTransferUtil.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileTransferUtil.java deleted file mode 100644 index e4729cb53..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileTransferUtil.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils; - -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.channels.FileChannel; - -public final class FileTransferUtil implements Closeable { - private final File output; - - private final FileChannel inputChannel; - - public FileTransferUtil(FileInputStream input, File output) { - this.output = output; - inputChannel = input.getChannel(); - } - - public void transferBytes(int offset, int length) throws IOException { - try (FileOutputStream out = new FileOutputStream(output, false)) { - inputChannel.transferTo(offset, length, out.getChannel()); - } - } - - @Override - public void close() throws IOException { - inputChannel.close(); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java index 65c81b842..e7995c675 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java @@ -51,7 +51,7 @@ void setUp() throws IOException { wrapped = mock(); exporter = FromDiskExporterImpl.builder( - TestData.getDefaultStorage(rootDir, SignalTypes.spans, clock)) + TestData.getStorage(rootDir, SignalTypes.spans, clock)) .setDeserializer(deserializer) .setExportFunction(wrapped::export) .build(); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java index 0d9e16723..d2b73f13d 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java @@ -37,7 +37,7 @@ class FolderManagerTest { @BeforeEach void setUp() { clock = mock(); - folderManager = new FolderManager(rootDir, TestData.getDefaultConfiguration(rootDir), clock); + folderManager = new FolderManager(rootDir, TestData.getConfiguration(rootDir), clock); } @Test @@ -45,7 +45,7 @@ void createWritableFile_withTimeMillisAsName() throws IOException { when(clock.now()).thenReturn(MILLISECONDS.toNanos(1000L)); WritableFile file = folderManager.createWritableFile(); - assertEquals("1000", file.getFileName()); + assertEquals("1000", file.getFile().getName()); } @Test diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java index 8e66dde04..75d86726e 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java @@ -6,8 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage; import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.config.TemporaryFileProvider; -import io.opentelemetry.contrib.disk.buffering.internal.files.DefaultTemporaryFileProvider; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.sdk.common.Clock; import java.io.File; @@ -21,22 +19,15 @@ public final class TestData { public static final int MAX_FILE_SIZE = 100; public static final int MAX_FOLDER_SIZE = 300; - public static StorageConfiguration getDefaultConfiguration(File rootDir) { - TemporaryFileProvider fileProvider = DefaultTemporaryFileProvider.getInstance(); - return getConfiguration(fileProvider, rootDir); - } - - public static Storage getDefaultStorage(File rootDir, SignalTypes types, Clock clock) + public static Storage getStorage(File rootDir, SignalTypes types, Clock clock) throws IOException { - TemporaryFileProvider fileProvider = DefaultTemporaryFileProvider.getInstance(); return Storage.builder(types) - .setStorageConfiguration(getConfiguration(fileProvider, rootDir)) + .setStorageConfiguration(getConfiguration(rootDir)) .setStorageClock(clock) .build(); } - public static StorageConfiguration getConfiguration( - TemporaryFileProvider fileProvider, File rootDir) { + public static StorageConfiguration getConfiguration(File rootDir) { return StorageConfiguration.builder() .setRootDir(rootDir) .setMaxFileAgeForWriteMillis(MAX_FILE_AGE_FOR_WRITE_MILLIS) @@ -44,7 +35,6 @@ public static StorageConfiguration getConfiguration( .setMaxFileAgeForReadMillis(MAX_FILE_AGE_FOR_READ_MILLIS) .setMaxFileSize(MAX_FILE_SIZE) .setMaxFolderSize(MAX_FOLDER_SIZE) - .setTemporaryFileProvider(fileProvider) .build(); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java index dd8cb02aa..6565543dd 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java @@ -12,13 +12,11 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; -import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; -import io.opentelemetry.contrib.disk.buffering.config.TemporaryFileProvider; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.DeserializationException; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.models.LogRecordDataImpl; @@ -42,10 +40,8 @@ class ReadableFileTest { @TempDir File dir; private File source; - private File temporaryFile; private ReadableFile readableFile; private Clock clock; - private TemporaryFileProvider temporaryFileProvider; private static final long CREATED_TIME_MILLIS = 1000L; private static final SignalSerializer SERIALIZER = SignalSerializer.ofLogs(); private static final SignalDeserializer DESERIALIZER = SignalDeserializer.ofLogs(); @@ -94,14 +90,9 @@ class ReadableFileTest { @BeforeEach void setUp() throws IOException { source = new File(dir, "sourceFile"); - temporaryFile = new File(dir, "temporaryFile"); addFileContents(source); - temporaryFileProvider = mock(); - when(temporaryFileProvider.createTemporaryFile(anyString())).thenReturn(temporaryFile); clock = mock(); - readableFile = - new ReadableFile( - source, CREATED_TIME_MILLIS, clock, getConfiguration(temporaryFileProvider, dir)); + readableFile = new ReadableFile(source, CREATED_TIME_MILLIS, clock, getConfiguration(dir)); } private static void addFileContents(File source) throws IOException { @@ -144,18 +135,18 @@ void whenProcessingFails_returnTryLaterStatus() throws IOException { ReadableResult.TRY_LATER, readableFile.readAndProcess(bytes -> ProcessResult.TRY_LATER)); } - @Test - void deleteTemporaryFileWhenClosing() throws IOException { - readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED); - readableFile.close(); - - assertFalse(temporaryFile.exists()); - } - @Test void readMultipleLinesAndRemoveThem() throws IOException { - readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED); - readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED); + readableFile.readAndProcess( + bytes -> { + assertDeserializedData(FIRST_LOG_RECORD, bytes); + return ProcessResult.SUCCEEDED; + }); + readableFile.readAndProcess( + bytes -> { + assertDeserializedData(SECOND_LOG_RECORD, bytes); + return ProcessResult.SUCCEEDED; + }); List logs = getRemainingDataAndClose(readableFile); @@ -198,8 +189,7 @@ void whenNoMoreLinesAvailableToRead_deleteOriginalFile_close_and_returnNoContent } ReadableFile emptyReadableFile = - new ReadableFile( - emptyFile, CREATED_TIME_MILLIS, clock, getConfiguration(temporaryFileProvider, dir)); + new ReadableFile(emptyFile, CREATED_TIME_MILLIS, clock, getConfiguration(dir)); assertEquals( ReadableResult.FAILED, emptyReadableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)); @@ -231,6 +221,15 @@ void whenReadingAfterClosed_returnFailedStatus() throws IOException { ReadableResult.FAILED, readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)); } + private static void assertDeserializedData(LogRecordData expected, byte[] bytes) { + try { + List deserialized = DESERIALIZER.deserialize(bytes); + assertEquals(expected, deserialized.get(0)); + } catch (DeserializationException e) { + throw new RuntimeException(e); + } + } + private static List getRemainingDataAndClose(ReadableFile readableFile) throws IOException { List result = new ArrayList<>(); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java index 8ff749c1e..6830b471a 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java @@ -43,7 +43,7 @@ void setUp() throws IOException { new WritableFile( new File(rootDir, String.valueOf(CREATED_TIME_MILLIS)), CREATED_TIME_MILLIS, - TestData.getDefaultConfiguration(rootDir), + TestData.getConfiguration(rootDir), clock); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java new file mode 100644 index 000000000..1d801db7b --- /dev/null +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java @@ -0,0 +1,82 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.StandardOpenOption; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +class FileStreamTest { + @TempDir File dir; + + @Test + void truncateTop() throws IOException { + String initialText = "1,2,3,4,5"; + byte[] readBuffer; + File temporaryFile = new File(dir, "temporaryFile"); + writeString(temporaryFile, initialText); + + FileStream stream = FileStream.create(temporaryFile); + + // Assert initial size + assertThat(stream.size()).isEqualTo(9); + + assertThat((char) stream.read()).asString().isEqualTo("1"); + assertThat(readString(temporaryFile)).isEqualTo(initialText); + assertThat(stream.size()).isEqualTo(9); + + // Truncate until current position + stream.truncateTop(); + assertThat(readString(temporaryFile)).isEqualTo(",2,3,4,5"); + assertThat(stream.size()).isEqualTo(8); + + // Truncate fixed size from the top + stream.truncateTop(3); + + // Ensure that the changes are made before closing the stream. + assertThat(readString(temporaryFile)).isEqualTo("3,4,5"); + assertThat(stream.size()).isEqualTo(5); + + // Truncate again + readBuffer = new byte[3]; + stream.read(readBuffer); + assertThat(readBuffer).asString().isEqualTo("3,4"); + assertThat(stream.size()).isEqualTo(5); + + stream.truncateTop(2); + + // Ensure that the changes are made before closing the stream. + assertThat(readString(temporaryFile)).isEqualTo("4,5"); + assertThat(stream.size()).isEqualTo(3); + + // Truncate all available data + stream.truncateTop(3); + assertThat(stream.size()).isEqualTo(0); + assertThat(readString(temporaryFile)).isEqualTo(""); + + stream.close(); + + // Ensure that the changes are kept after closing the stream. + assertThat(readString(temporaryFile)).isEqualTo(""); + } + + private static void writeString(File file, String text) throws IOException { + Files.write( + file.toPath(), text.getBytes(StandardCharsets.UTF_8), StandardOpenOption.CREATE_NEW); + } + + @NotNull + private static String readString(File file) throws IOException { + return new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8); + } +} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java index d413c4aa6..adfc8fb2f 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java @@ -26,7 +26,7 @@ protected byte[] serialize(SIGNAL_SDK_ITEM... items) { protected List deserialize(byte[] source) { try (ByteArrayInputStream in = new ByteArrayInputStream(source)) { StreamReader streamReader = DelimitedProtoStreamReader.Factory.getInstance().create(in); - return getDeserializer().deserialize(Objects.requireNonNull(streamReader.read()).content); + return getDeserializer().deserialize(Objects.requireNonNull(streamReader.readNext()).content); } catch (IOException e) { throw new RuntimeException(e); } From 312ecac75206900f20bba72d451b3e25dc828acc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Jun 2025 19:36:28 -0700 Subject: [PATCH 063/371] chore(deps): update all patch versions (#1936) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- disk-buffering/build.gradle.kts | 2 +- opamp-client/build.gradle.kts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7906b98c5..bd12e8f8a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -38,7 +38,7 @@ jobs: uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - name: Initialize CodeQL - uses: github/codeql-action/init@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: languages: java, actions # using "latest" helps to keep up with the latest Kotlin support @@ -52,4 +52,4 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 839b64885..352cf133f 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18 + uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: sarif_file: results.sarif diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 09f424aee..0ca194b25 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.github.johnrengelman.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.1" + id("com.squareup.wire") version "5.3.2" } description = "Exporter implementations that store signals on disk" diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index a1f0c698e..c3013b245 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import groovy.json.JsonSlurper plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.1" + id("com.squareup.wire") version "5.3.2" } description = "Client implementation of the OpAMP spec." From 5581d561ab2cf2e94ae1bb24a8991377184eebd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 4 Jun 2025 04:45:27 +0200 Subject: [PATCH 064/371] Implementing ExtendedLogRecordData (#1918) --- disk-buffering/build.gradle.kts | 1 + .../mapping/logs/models/LogRecordDataImpl.java | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 0ca194b25..2551a4643 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -17,6 +17,7 @@ val protos by configurations.creating dependencies { api("io.opentelemetry:opentelemetry-sdk") + implementation("io.opentelemetry:opentelemetry-api-incubator") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") signature("com.toasttab.android:gummy-bears-api-21:0.6.1:coreLib@signature") diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java index 51322b41e..d703919a8 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java @@ -8,15 +8,16 @@ import com.google.auto.value.AutoValue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.incubator.common.ExtendedAttributes; import io.opentelemetry.api.logs.Severity; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; -import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.data.internal.ExtendedLogRecordData; import io.opentelemetry.sdk.resources.Resource; import javax.annotation.Nullable; @AutoValue -public abstract class LogRecordDataImpl implements LogRecordData { +public abstract class LogRecordDataImpl implements ExtendedLogRecordData { public static Builder builder() { return new AutoValue_LogRecordDataImpl.Builder(); @@ -30,6 +31,17 @@ public io.opentelemetry.sdk.logs.data.Body getBody() { : io.opentelemetry.sdk.logs.data.Body.string(valueBody.asString()); } + @Override + public ExtendedAttributes getExtendedAttributes() { + return ExtendedAttributes.builder().putAll(getAttributes()).build(); + } + + // It's only deprecated in the incubating interface for extended attributes, which are not yet + // supported in this module. + @SuppressWarnings("deprecation") + @Override + public abstract Attributes getAttributes(); + @Override @Nullable public abstract Value getBodyValue(); From 91276a92f93484c5a4b4e1941720c69bf94b6adf Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Wed, 4 Jun 2025 18:32:17 +0100 Subject: [PATCH 065/371] Add jackshirazi to opamp component_owners (#1937) --- .github.amrom.workers.devponent_owners.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github.amrom.workers.devponent_owners.yml b/.github.amrom.workers.devponent_owners.yml index de4544864..39388bf7f 100644 --- a/.github.amrom.workers.devponent_owners.yml +++ b/.github.amrom.workers.devponent_owners.yml @@ -89,3 +89,4 @@ components: - sylvainjuge opamp-client: - LikeTheSalad + - jackshirazi From 7b8bbf7d4525c9350518f1a440f16e81105d166a Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Thu, 5 Jun 2025 11:20:55 -0400 Subject: [PATCH 066/371] Update ConfigurableOptions to read from ConfigProperties (#1904) --- .../contrib/gcp/auth/ConfigurableOption.java | 37 ++++++----- ...thAutoConfigurationCustomizerProvider.java | 65 +++++++++++++------ ...toConfigurationCustomizerProviderTest.java | 1 - 3 files changed, 68 insertions(+), 35 deletions(-) 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 5f4a627e1..639207909 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 @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.gcp.auth; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import java.util.Locale; import java.util.Optional; @@ -14,7 +15,7 @@ * An enum representing configurable options for a GCP Authentication Extension. Each option has a * user-readable name and can be configured using environment variables or system properties. */ -public enum ConfigurableOption { +enum ConfigurableOption { /** * Represents the Google Cloud Project ID option. Can be configured using the environment variable * `GOOGLE_CLOUD_PROJECT` or the system property `google.cloud.project`. @@ -50,7 +51,7 @@ public enum ConfigurableOption { * configured using the environment variable `GOOGLE_OTEL_AUTH_TARGET_SIGNALS` or the system * property `google.otel.auth.target.signals`. */ - GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Auth Extension"); + GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Authentication Extension"); private final String userReadableName; private final String environmentVariableName; @@ -60,7 +61,7 @@ public enum ConfigurableOption { this.userReadableName = userReadableName; this.environmentVariableName = this.name(); this.systemPropertyName = - this.environmentVariableName.toLowerCase(Locale.ENGLISH).replace('_', '.'); + this.environmentVariableName.toLowerCase(Locale.ROOT).replace('_', '.'); } /** @@ -81,6 +82,15 @@ String getSystemProperty() { return this.systemPropertyName; } + /** + * Returns the user readable name associated with this option. + * + * @return the user readable name (e.g., "Google Cloud Quota Project ID") + */ + String getUserReadableName() { + return this.userReadableName; + } + /** * Retrieves the configured value for this option. This method checks the environment variable * first and then the system property. @@ -89,14 +99,10 @@ String getSystemProperty() { * @throws ConfigurationException if neither the environment variable nor the system property is * set. */ - String getConfiguredValue() { - String envVar = System.getenv(this.getEnvironmentVariable()); - String sysProp = System.getProperty(this.getSystemProperty()); - - if (envVar != null && !envVar.isEmpty()) { - return envVar; - } else if (sysProp != null && !sysProp.isEmpty()) { - return sysProp; + String getConfiguredValue(ConfigProperties configProperties) { + String configuredValue = configProperties.getString(this.getSystemProperty()); + if (configuredValue != null && !configuredValue.isEmpty()) { + return configuredValue; } else { throw new ConfigurationException( String.format( @@ -115,9 +121,10 @@ String getConfiguredValue() { * @return The configured value for the option, obtained from the environment variable, system * property, or the fallback function, in that order of precedence. */ - String getConfiguredValueWithFallback(Supplier fallback) { + String getConfiguredValueWithFallback( + ConfigProperties configProperties, Supplier fallback) { try { - return this.getConfiguredValue(); + return this.getConfiguredValue(configProperties); } catch (ConfigurationException e) { return fallback.get(); } @@ -131,9 +138,9 @@ String getConfiguredValueWithFallback(Supplier fallback) { * @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() { + Optional getConfiguredValueAsOptional(ConfigProperties configProperties) { try { - return Optional.of(this.getConfiguredValue()); + return Optional.of(this.getConfiguredValue(configProperties)); } catch (ConfigurationException e) { return Optional.empty(); } 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 d6c68e54f..1de583029 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 @@ -30,6 +30,8 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.logging.Level; +import java.util.logging.Logger; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -50,6 +52,15 @@ public class GcpAuthAutoConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider { + private static final Logger logger = + Logger.getLogger(GcpAuthAutoConfigurationCustomizerProvider.class.getName()); + private static final String SIGNAL_TARGET_WARNING_FIX_SUGGESTION = + String.format( + "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.", + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getUserReadableName(), + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getEnvironmentVariable(), + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty()); + static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project"; static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id"; @@ -93,10 +104,11 @@ public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { } autoConfiguration .addSpanExporterCustomizer( - (spanExporter, configProperties) -> customizeSpanExporter(spanExporter, credentials)) + (spanExporter, configProperties) -> + customizeSpanExporter(spanExporter, credentials, configProperties)) .addMetricExporterCustomizer( (metricExporter, configProperties) -> - customizeMetricExporter(metricExporter, credentials)) + customizeMetricExporter(metricExporter, credentials, configProperties)) .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); } @@ -106,26 +118,38 @@ public int order() { } private static SpanExporter customizeSpanExporter( - SpanExporter exporter, GoogleCredentials credentials) { - if (isSignalTargeted(SIGNAL_TYPE_TRACES)) { - return addAuthorizationHeaders(exporter, credentials); + SpanExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) { + if (isSignalTargeted(SIGNAL_TYPE_TRACES, 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) { - if (isSignalTargeted(SIGNAL_TYPE_METRICS)) { - return addAuthorizationHeaders(exporter, credentials); + MetricExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) { + if (isSignalTargeted(SIGNAL_TYPE_METRICS, configProperties)) { + return addAuthorizationHeaders(exporter, credentials, configProperties); + } 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); } return exporter; } // Checks if the auth extension is configured to target the passed signal for authentication. - private static boolean isSignalTargeted(String checkSignal) { + private static boolean isSignalTargeted(String checkSignal, ConfigProperties configProperties) { String userSpecifiedTargetedSignals = ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback( - () -> SIGNAL_TYPE_ALL); + configProperties, () -> SIGNAL_TYPE_ALL); return Arrays.stream(userSpecifiedTargetedSignals.split(",")) .map(String::trim) .anyMatch( @@ -136,16 +160,16 @@ private static boolean isSignalTargeted(String checkSignal) { // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and // OtlpHttpSpanExporter. private static SpanExporter addAuthorizationHeaders( - SpanExporter exporter, GoogleCredentials credentials) { + SpanExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) { if (exporter instanceof OtlpHttpSpanExporter) { OtlpHttpSpanExporterBuilder builder = ((OtlpHttpSpanExporter) exporter) - .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties)); return builder.build(); } else if (exporter instanceof OtlpGrpcSpanExporter) { OtlpGrpcSpanExporterBuilder builder = ((OtlpGrpcSpanExporter) exporter) - .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties)); return builder.build(); } return exporter; @@ -154,22 +178,23 @@ private static SpanExporter addAuthorizationHeaders( // Adds authorization headers to the calls made by the OtlpGrpcMetricExporter and // OtlpHttpMetricExporter. private static MetricExporter addAuthorizationHeaders( - MetricExporter exporter, GoogleCredentials credentials) { + MetricExporter exporter, GoogleCredentials credentials, ConfigProperties configProperties) { if (exporter instanceof OtlpHttpMetricExporter) { OtlpHttpMetricExporterBuilder builder = ((OtlpHttpMetricExporter) exporter) - .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties)); return builder.build(); } else if (exporter instanceof OtlpGrpcMetricExporter) { OtlpGrpcMetricExporterBuilder builder = ((OtlpGrpcMetricExporter) exporter) - .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials, configProperties)); return builder.build(); } return exporter; } - private static Map getRequiredHeaderMap(GoogleCredentials credentials) { + private static Map getRequiredHeaderMap( + GoogleCredentials credentials, ConfigProperties configProperties) { Map> gcpHeaders; try { // this also refreshes the credentials, if required @@ -192,7 +217,8 @@ private static Map getRequiredHeaderMap(GoogleCredentials creden // system properties. if (!flattenedHeaders.containsKey(QUOTA_USER_PROJECT_HEADER)) { Optional maybeConfiguredQuotaProjectId = - ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueAsOptional(); + ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueAsOptional( + configProperties); maybeConfiguredQuotaProjectId.ifPresent( configuredQuotaProjectId -> flattenedHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId)); @@ -202,7 +228,8 @@ private static Map getRequiredHeaderMap(GoogleCredentials creden // 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(); + String gcpProjectId = + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue(configProperties); Resource res = Resource.create( Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId)); diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index de96dc382..6cc1797f0 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -941,7 +941,6 @@ private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( spanExporter, OtlpHttpMetricExporter.getDefault(), customOTelProperties); } - @SuppressWarnings("UnusedMethod") private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(MetricExporter metricExporter) { return buildOpenTelemetrySdkWithExporter( OtlpHttpSpanExporter.getDefault(), metricExporter, defaultOtelPropertiesMetricExporter); From e1c2db4aa7cee80614ecfe5619f2a3074ed6712c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 6 Jun 2025 14:30:25 -0700 Subject: [PATCH 067/371] Reduce repo settings we need to doc (#1939) --- .github/repository-settings.md | 22 +++++----------------- 1 file changed, 5 insertions(+), 17 deletions(-) diff --git a/.github/repository-settings.md b/.github/repository-settings.md index 0e3d9240c..22299364d 100644 --- a/.github/repository-settings.md +++ b/.github/repository-settings.md @@ -1,24 +1,12 @@ # Repository settings -Same -as [opentelemetry-java-instrumentation repository settings](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/.github/repository-settings.md#repository-settings), -except for +This document describes any changes that have been made to the +settings in this repository outside the settings tracked in the +private admin repo. -- The rules for `gh-pages` and `cloudfoundry` branches are not relevant in this repository. +## Merge queue for `main` -and the enablement of merge queues below. - -## Merge queue - -Needs to be enabled using classic branch protection (instead of rule set) -because of our use of the classic branch protection "Restrict who can push to matching branches" -which otherwise will block the merge queue from merging to main. - -### Restrict branch creation - -- Additional exclusion for `gh-readonly-queue/main/pr-*` - -### Classic branch protection for `main` +[The admin repo doesn't currently support tracking merge queue settings.] - Require merge queue: CHECKED - Build concurrency: 5 From 82cdc8959d158d83b9a3331d9456a284ad983b5f Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:22:19 -0700 Subject: [PATCH 068/371] Update repos for sonatype central (#1942) --- build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/build.gradle.kts b/build.gradle.kts index 6e586f336..2422f54ff 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -14,7 +14,10 @@ nexusPublishing { packageGroup.set("io.opentelemetry") repositories { + // see https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration sonatype { + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) username.set(System.getenv("SONATYPE_USER")) password.set(System.getenv("SONATYPE_KEY")) } From 0a37bb9ed29bf9d8fea2d64114ddb2566b7aae80 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 6 Jun 2025 22:16:44 -0700 Subject: [PATCH 069/371] Fix workflow when issue is a PR (#1943) --- .github/workflows/issue-management-feedback-label.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/issue-management-feedback-label.yml b/.github/workflows/issue-management-feedback-label.yml index 35fa82926..fb4a03757 100644 --- a/.github/workflows/issue-management-feedback-label.yml +++ b/.github/workflows/issue-management-feedback-label.yml @@ -12,6 +12,7 @@ jobs: permissions: contents: read issues: write + pull-requests: write if: > contains(github.event.issue.labels.*.name, 'needs author feedback') && github.event.comment.user.login == github.event.issue.user.login From ebfe715c2c9ba6795927019c6a706f2ed8bba666 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:23:10 +0300 Subject: [PATCH 070/371] fix(deps): update all patch versions (#1945) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- disk-buffering/build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- opamp-client/build.gradle.kts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 9e137dc98..5fb35e8c4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.4") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.2.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") - implementation("org.owasp:dependency-check-gradle:12.1.1") + implementation("org.owasp:dependency-check-gradle:12.1.2") } spotless { diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 05412a214..59439264e 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -135,7 +135,7 @@ testing { dependencies { implementation(project(project.path)) - implementation(enforcedPlatform("org.junit:junit-bom:5.13.0")) + implementation(enforcedPlatform("org.junit:junit-bom:5.13.1")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.1")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 2551a4643..44b0fbbf1 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.github.johnrengelman.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.2" + id("com.squareup.wire") version "5.3.3" } description = "Exporter implementations that store signals on disk" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9128c7d42..3735f265b 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=845952a9d6afa783db70bb3b0effaae45ae5542ca2bb7929619e8af49cb634cf -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip +distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index c3013b245..990ab32b5 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import groovy.json.JsonSlurper plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.2" + id("com.squareup.wire") version "5.3.3" } description = "Client implementation of the OpAMP spec." From 2f8485769494ef21d39c9ca7c93330d37141403e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:23:41 +0300 Subject: [PATCH 071/371] fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.37.0 (#1946) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 8f4a9d90c..c1b9fe742 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.36.0") + implementation("com.google.auth:google-auth-library-oauth2-http:1.37.0") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From 90337511619e888aa4d5c9559f8c7e8e7fa3bec7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 06:24:44 +0000 Subject: [PATCH 072/371] fix(deps): update dependency com.toasttab.android:gummy-bears-api-21 to v0.12.0 (#1947) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- disk-buffering/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 44b0fbbf1..b3a842b1d 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -20,7 +20,7 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api-incubator") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") - signature("com.toasttab.android:gummy-bears-api-21:0.6.1:coreLib@signature") + signature("com.toasttab.android:gummy-bears-api-21:0.12.0:coreLib@signature") testImplementation("org.mockito:mockito-inline") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") From ce537f25e15ddb6b373316a7c6504968784b2852 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 09:25:20 +0300 Subject: [PATCH 073/371] fix(deps): update dependency org.bouncycastle:bcprov-jdk18on to v1.81 (#1949) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- jmx-scraper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index ba40ddcac..53bb999f5 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -40,7 +40,7 @@ testing { implementation("com.linecorp.armeria:armeria-junit5") implementation("com.linecorp.armeria:armeria-grpc") implementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") - implementation("org.bouncycastle:bcprov-jdk18on:1.80") + implementation("org.bouncycastle:bcprov-jdk18on:1.81") implementation("org.bouncycastle:bcpkix-jdk18on:1.80") } } From 2ef34b880471dc8258aee289f7d01233f9d04d00 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 9 Jun 2025 12:52:30 +0300 Subject: [PATCH 074/371] fix(deps): update dependency org.bouncycastle:bcpkix-jdk18on to v1.81 (#1948) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- jmx-scraper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 53bb999f5..d58fa6490 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -41,7 +41,7 @@ testing { implementation("com.linecorp.armeria:armeria-grpc") implementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") implementation("org.bouncycastle:bcprov-jdk18on:1.81") - implementation("org.bouncycastle:bcpkix-jdk18on:1.80") + implementation("org.bouncycastle:bcpkix-jdk18on:1.81") } } } From 9abec35e315b7511de26f9201caca9a794b8b0eb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 10 Jun 2025 07:47:36 +0300 Subject: [PATCH 075/371] fix(deps): update all patch versions to v1.15.1 (#1952) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- micrometer-meter-provider/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index eed860e4b..a9f13addc 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.15.0") + testImplementation("io.micrometer:micrometer-core:1.15.1") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.15.0") + implementation("io.micrometer:micrometer-registry-prometheus:1.15.1") } } } From 49fa87b916c7bb355114a1e386381b7289194cff Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 9 Jun 2025 21:49:06 -0700 Subject: [PATCH 076/371] Update snapshot location (#1951) --- CONTRIBUTING.md | 4 ++-- RELEASING.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f482397c7..6d8a6a806 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,8 +40,8 @@ In order to build and test this whole repository you need JDK 11+. For developers testing code changes before a release is complete, there are snapshot builds of the `main` branch. They are available from -the Sonatype OSS snapshots repository at `https://oss.sonatype.org/content/repositories/snapshots/` -([browse](https://oss.sonatype.org/content/repositories/snapshots/io/opentelemetry/contrib/)) +the Sonatype snapshot repository at `https://central.sonatype.com/repository/maven-snapshots/` +([browse](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/contrib/)). #### Building from source diff --git a/RELEASING.md b/RELEASING.md index afb1b8162..70d044163 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -7,8 +7,8 @@ The version is specified in [version.gradle.kts](version.gradle.kts). ## Snapshot builds Every successful CI build of the main branch automatically executes `./gradlew publishToSonatype` -as the last step, which publishes a snapshot build to -[Sonatype OSS snapshots repository](https://oss.sonatype.org/content/repositories/snapshots/io/opentelemetry/contrib/). +as the last step, which publishes a snapshot build to the +[Sonatype snapshot repository](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/contrib/). ## Release cadence From 9c487385ac4653a4a928090d4acb0dcd04ce62bd Mon Sep 17 00:00:00 2001 From: Leonardo Serrano Date: Tue, 10 Jun 2025 10:43:46 -0700 Subject: [PATCH 077/371] Add missing EventName to disk-buffering LogRecordDataMapper (#1950) --- .../mapping/logs/LogRecordDataMapper.java | 4 +++ .../logs/models/LogRecordDataImpl.java | 6 ++++ .../mapping/logs/LogRecordDataMapperTest.java | 1 + .../mapping/logs/ProtoLogsDataMapperTest.java | 32 +++++++++++++++++++ .../LogRecordDataSerializerTest.java | 6 ++-- .../storage/files/ReadableFileTest.java | 3 ++ 6 files changed, 50 insertions(+), 2 deletions(-) diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapper.java index 06ff85847..d9cafa4a4 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapper.java @@ -64,6 +64,9 @@ private static void addExtrasToProtoBuilder(LogRecordData source, LogRecord.Buil target.trace_id(ByteStringMapper.getInstance().stringToProto(spanContext.getTraceId())); target.dropped_attributes_count( source.getTotalAttributeCount() - source.getAttributes().size()); + if (source.getEventName() != null) { + target.event_name(source.getEventName()); + } } public LogRecordData mapToSdk( @@ -99,6 +102,7 @@ private static void addExtrasToSdkItemBuilder( target.setTotalAttributeCount(source.dropped_attributes_count + attributes.size()); target.setResource(resource); target.setInstrumentationScopeInfo(scopeInfo); + target.setEventName(source.event_name); } private static AnyValue bodyToAnyValue(Value body) { diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java index d703919a8..2979f96fb 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/models/LogRecordDataImpl.java @@ -46,6 +46,10 @@ public ExtendedAttributes getExtendedAttributes() { @Nullable public abstract Value getBodyValue(); + @Override + @Nullable + public abstract String getEventName(); + @AutoValue.Builder public abstract static class Builder { public abstract Builder setResource(Resource value); @@ -68,6 +72,8 @@ public abstract static class Builder { public abstract Builder setTotalAttributeCount(Integer value); + public abstract Builder setEventName(String value); + public abstract LogRecordDataImpl build(); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java index 3eb588b45..0041c61bf 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java @@ -31,6 +31,7 @@ class LogRecordDataMapperTest { .setTimestampEpochNanos(100L) .setObservedTimestampEpochNanos(200L) .setTotalAttributeCount(3) + .setEventName("my.event.name") .build(); @Test diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java index 45c3f6e5e..26c73502e 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java @@ -37,6 +37,7 @@ class ProtoLogsDataMapperTest { .setTimestampEpochNanos(100L) .setObservedTimestampEpochNanos(200L) .setTotalAttributeCount(3) + .setEventName("") .build(); private static final LogRecordData OTHER_LOG_RECORD = @@ -51,6 +52,7 @@ class ProtoLogsDataMapperTest { .setTimestampEpochNanos(100L) .setObservedTimestampEpochNanos(200L) .setTotalAttributeCount(3) + .setEventName("") .build(); private static final LogRecordData LOG_RECORD_WITH_DIFFERENT_SCOPE_SAME_RESOURCE = @@ -65,6 +67,7 @@ class ProtoLogsDataMapperTest { .setTimestampEpochNanos(100L) .setObservedTimestampEpochNanos(200L) .setTotalAttributeCount(3) + .setEventName("") .build(); private static final LogRecordData LOG_RECORD_WITH_DIFFERENT_RESOURCE = @@ -79,6 +82,22 @@ class ProtoLogsDataMapperTest { .setTimestampEpochNanos(100L) .setObservedTimestampEpochNanos(200L) .setTotalAttributeCount(3) + .setEventName("") + .build(); + + private static final LogRecordData LOG_RECORD_WITH_EVENT_NAME = + LogRecordDataImpl.builder() + .setResource(TestData.RESOURCE_FULL) + .setSpanContext(TestData.SPAN_CONTEXT) + .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) + .setAttributes(TestData.ATTRIBUTES) + .setBodyValue(Value.of("Log body")) + .setSeverity(Severity.DEBUG) + .setSeverityText("Log severity text") + .setTimestampEpochNanos(100L) + .setObservedTimestampEpochNanos(200L) + .setTotalAttributeCount(3) + .setEventName("test.event.name") .build(); @Test @@ -160,6 +179,19 @@ void verifyMultipleLogsWithDifferentResource() { assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } + @Test + void verifyLogWithEventName() { + List signals = Collections.singletonList(LOG_RECORD_WITH_EVENT_NAME); + + LogsData result = mapToProto(signals); + + List resourceLogsList = result.resource_logs; + LogRecord firstLog = resourceLogsList.get(0).scope_logs.get(0).log_records.get(0); + + assertEquals("test.event.name", firstLog.event_name); + assertThat(mapFromProto(result)).containsExactlyInAnyOrderElementsOf(signals); + } + private static LogsData mapToProto(Collection signals) { return ProtoLogsDataMapper.getInstance().toProto(signals); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializerTest.java index 1b52bb219..ec3212001 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializerTest.java @@ -28,9 +28,10 @@ class LogRecordDataSerializerTest extends BaseSignalSerializerTest Date: Tue, 10 Jun 2025 12:52:03 -0700 Subject: [PATCH 078/371] Faster CodeQL (#1944) --- .github/workflows/codeql.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index bd12e8f8a..5158ca483 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -6,6 +6,9 @@ on: - main - release/* pull_request: + branches: + - main + - release/* # TODO (trask) adding this to the merge queue causes the merge queue to fail # see related issues # - https://github.com/github/codeql-action/issues/1572 @@ -20,32 +23,42 @@ permissions: jobs: analyze: + name: Analyze (${{ matrix.language }}) permissions: contents: read actions: read # for github/codeql-action/init to get workflow details security-events: write # for github/codeql-action/analyze to upload SARIF results + strategy: + fail-fast: false + matrix: + include: + - language: actions + - language: java runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set up Java 17 + if: matrix.language == 'java' uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: temurin java-version: 17 - name: Set up gradle + if: matrix.language == 'java' uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 - name: Initialize CodeQL uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 with: - languages: java, actions + languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support # see https://github.com/github/codeql-action/issues/1555#issuecomment-1452228433 tools: latest - name: Assemble + if: matrix.language == 'java' # --no-build-cache is required for codeql to analyze all modules # --no-daemon is required for codeql to observe the compilation # (see https://docs.github.com/en/code-security/codeql-cli/getting-started-with-the-codeql-cli/preparing-your-code-for-codeql-analysis#specifying-build-commands) @@ -53,3 +66,5 @@ jobs: - name: Perform CodeQL analysis uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + with: + category: "/language:${{matrix.language}}" From a4cc53feef3b7e3d203db33488ac8a39a135408b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 11 Jun 2025 08:12:00 +0300 Subject: [PATCH 079/371] fix(deps): update dependency org.owasp:dependency-check-gradle to v12.1.3 (#1953) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 5fb35e8c4..0b6823fc8 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.4") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.2.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") - implementation("org.owasp:dependency-check-gradle:12.1.2") + implementation("org.owasp:dependency-check-gradle:12.1.3") } spotless { From 6c31f42ba83e443108f4975b8a29ee5d1286e425 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 12 Jun 2025 07:53:11 +0300 Subject: [PATCH 080/371] chore(deps): update gradle/actions action to v4.4.1 (#1958) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/build.yml | 8 ++++---- .github/workflows/codeql.yml | 2 +- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index b7a08afbc..755959dbf 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -25,7 +25,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 with: cache-read-only: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cebd8993d..77c3b86fc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 with: cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Gradle build and test @@ -65,7 +65,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 with: cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Gradle test @@ -87,7 +87,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 with: cache-read-only: ${{ github.event_name == 'pull_request' }} @@ -139,7 +139,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 # skipping release branches because the versions in those branches are not snapshots # (also this skips pull requests) if: ${{ github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-contrib' }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 5158ca483..e41f3c1fb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: - name: Set up gradle if: matrix.language == 'java' - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 3e3648f9e..75c7728cd 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: gradle/actions/wrapper-validation@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + - uses: gradle/actions/wrapper-validation@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index 46e12c4d6..a026cb498 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -27,7 +27,7 @@ jobs: run: | sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - run: ./gradlew dependencyCheckAnalyze env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8fd4f18c0..af1ca5012 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Gradle build run: ./gradlew build @@ -41,7 +41,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Integration test run: ./gradlew integrationTest @@ -124,7 +124,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 + uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Build and publish artifacts run: ./gradlew assemble publishToSonatype closeAndReleaseSonatypeStagingRepository env: From b73618392b2c2f61bca4777e7b6ee969926d1413 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:19:21 +0300 Subject: [PATCH 081/371] chore(deps): update github/codeql-action action to v3.29.0 (#1962) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e41f3c1fb..9b8e7cbb9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL - uses: github/codeql-action/init@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 352cf133f..cff5b894f 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@fca7ace96b7d713c7035871441bd52efbe39e27e # v3.28.19 + uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 with: sarif_file: results.sarif From 4b992a7a61b66212eaa42d03fa3eba526719ddc5 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 08:19:44 +0300 Subject: [PATCH 082/371] fix(deps): update dependency com.fasterxml.jackson:jackson-bom to v2.19.1 (#1961) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 78e2b4579..3e3a57cd7 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { // (the constraints section below doesn't have this issue, and will only show up // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) - api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.0")) + api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.1")) constraints { api("io.opentelemetry.semconv:opentelemetry-semconv:${semconvVersion}") From 3856e1d003ee4f004e044f1e560491fb67ffb7f4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 16 Jun 2025 12:45:18 -0700 Subject: [PATCH 083/371] fix(deps): update semconvversion (#1963) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 3e3a57cd7..6a496980c 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } val otelInstrumentationVersion = "2.16.0-alpha" -val semconvVersion = "1.32.0" +val semconvVersion = "1.34.0" javaPlatform { allowDependencies() From a26d257a4482120110e822d0c138144085bf33ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Mon, 16 Jun 2025 23:15:00 +0200 Subject: [PATCH 084/371] OpAMP HTTP service (#1928) Co-authored-by: Lauri Tulmin --- opamp-client/build.gradle.kts | 2 + .../connectivity/http/HttpErrorException.java | 27 + .../connectivity/http/HttpSender.java | 31 ++ .../connectivity/http/OkHttpSender.java | 135 +++++ .../connectivity/http/RetryAfterParser.java | 49 ++ .../client/internal/request/Request.java | 19 + .../request/delay/AcceptsDelaySuggestion.java | 18 + .../request/delay/FixedPeriodicDelay.java | 24 + .../internal/request/delay/PeriodicDelay.java | 18 + .../request/service/HttpRequestService.java | 273 ++++++++++ .../request/service/RequestService.java | 76 +++ .../response/OpampServerResponseError.java | 19 + .../client/internal/response/Response.java | 18 + .../client/internal/tools/SystemTime.java | 21 + .../http/RetryAfterParserTest.java | 34 ++ .../service/HttpRequestServiceTest.java | 492 ++++++++++++++++++ 16 files changed, 1256 insertions(+) create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpErrorException.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpSender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParser.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Request.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/AcceptsDelaySuggestion.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/FixedPeriodicDelay.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/PeriodicDelay.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/Response.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/tools/SystemTime.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParserTest.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 990ab32b5..0e33a87a6 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -12,8 +12,10 @@ description = "Client implementation of the OpAMP spec." otelJava.moduleName.set("io.opentelemetry.contrib.opamp.client") dependencies { + implementation("com.squareup.okhttp3:okhttp") annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") + testImplementation("org.mockito:mockito-inline") } val opampReleaseInfo = tasks.register("opampLastReleaseInfo") { diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpErrorException.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpErrorException.java new file mode 100644 index 000000000..c1104118c --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpErrorException.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.http; + +public class HttpErrorException extends Exception { + private final int errorCode; + + private static final long serialVersionUID = 1L; + + public int getErrorCode() { + return errorCode; + } + + /** + * Constructs an HTTP error related exception. + * + * @param errorCode The HTTP error code. + * @param message The HTTP error message associated with the code. + */ + public HttpErrorException(int errorCode, String message) { + super(message); + this.errorCode = errorCode; + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpSender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpSender.java new file mode 100644 index 000000000..704d172ac --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/HttpSender.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.http; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.concurrent.CompletableFuture; + +public interface HttpSender { + + CompletableFuture send(BodyWriter writer, int contentLength); + + interface BodyWriter { + void writeTo(OutputStream outputStream) throws IOException; + } + + interface Response extends Closeable { + int statusCode(); + + String statusMessage(); + + InputStream bodyInputStream(); + + String getHeader(String name); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java new file mode 100644 index 000000000..3e8f9a174 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.http; + +import java.io.IOException; +import java.io.InputStream; +import java.util.concurrent.CompletableFuture; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.RequestBody; +import okio.BufferedSink; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +public final class OkHttpSender implements HttpSender { + private final OkHttpClient client; + private final String url; + + public static OkHttpSender create(String url) { + return create(url, new OkHttpClient()); + } + + public static OkHttpSender create(String url, OkHttpClient client) { + return new OkHttpSender(url, client); + } + + private static final String CONTENT_TYPE = "application/x-protobuf"; + private static final MediaType MEDIA_TYPE = MediaType.parse(CONTENT_TYPE); + + private OkHttpSender(String url, OkHttpClient client) { + this.url = url; + this.client = client; + } + + @Override + public CompletableFuture send(BodyWriter writer, int contentLength) { + CompletableFuture future = new CompletableFuture<>(); + okhttp3.Request.Builder builder = new okhttp3.Request.Builder().url(url); + builder.addHeader("Content-Type", CONTENT_TYPE); + + RequestBody body = new RawRequestBody(writer, contentLength, MEDIA_TYPE); + builder.post(body); + + client + .newCall(builder.build()) + .enqueue( + new Callback() { + @Override + public void onResponse(@NotNull Call call, @NotNull okhttp3.Response response) { + if (response.isSuccessful() && response.body() != null) { + future.complete(new OkHttpResponse(response)); + } else { + future.completeExceptionally( + new HttpErrorException(response.code(), response.message())); + } + } + + @Override + public void onFailure(@NotNull Call call, @NotNull IOException e) { + future.completeExceptionally(e); + } + }); + + return future; + } + + private static class OkHttpResponse implements Response { + private final okhttp3.Response response; + + private OkHttpResponse(okhttp3.Response response) { + if (response.body() == null) { + throw new IllegalStateException("null response body not expected"); + } + this.response = response; + } + + @Override + public int statusCode() { + return response.code(); + } + + @Override + public String statusMessage() { + return response.message(); + } + + @Override + public InputStream bodyInputStream() { + return response.body().byteStream(); + } + + @Override + public String getHeader(String name) { + return response.headers().get(name); + } + + @Override + public void close() { + response.close(); + } + } + + private static class RawRequestBody extends RequestBody { + private final BodyWriter writer; + private final int contentLength; + private final MediaType contentType; + + private RawRequestBody(BodyWriter writer, int contentLength, MediaType contentType) { + this.writer = writer; + this.contentLength = contentLength; + this.contentType = contentType; + } + + @Nullable + @Override + public MediaType contentType() { + return contentType; + } + + @Override + public long contentLength() { + return contentLength; + } + + @Override + public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException { + writer.writeTo(bufferedSink.outputStream()); + } + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParser.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParser.java new file mode 100644 index 000000000..6cc2d1e3b --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParser.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.http; + +import io.opentelemetry.opamp.client.internal.tools.SystemTime; +import java.time.Duration; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.Optional; +import java.util.regex.Pattern; + +public final class RetryAfterParser { + private final SystemTime systemTime; + private static final Pattern SECONDS_PATTERN = Pattern.compile("\\d+"); + private static final Pattern DATE_PATTERN = + Pattern.compile( + "[A-Za-z]{3}, [0-3][0-9] [A-Za-z]{3} [0-9]{4} [0-2][0-9]:[0-5][0-9]:[0-5][0-9] GMT"); + private static final DateTimeFormatter DATE_FORMAT = + DateTimeFormatter.ofPattern("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); + + public static RetryAfterParser getInstance() { + return new RetryAfterParser(SystemTime.getInstance()); + } + + RetryAfterParser(SystemTime systemTime) { + this.systemTime = systemTime; + } + + public Optional tryParse(String value) { + Duration duration = null; + if (SECONDS_PATTERN.matcher(value).matches()) { + duration = Duration.ofSeconds(Long.parseLong(value)); + } else if (DATE_PATTERN.matcher(value).matches()) { + long difference = toMilliseconds(value) - systemTime.getCurrentTimeMillis(); + if (difference > 0) { + duration = Duration.ofMillis(difference); + } + } + return Optional.ofNullable(duration); + } + + private static long toMilliseconds(String value) { + return ZonedDateTime.parse(value, DATE_FORMAT).toInstant().toEpochMilli(); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Request.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Request.java new file mode 100644 index 000000000..d6a3c7aed --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Request.java @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request; + +import com.google.auto.value.AutoValue; +import opamp.proto.AgentToServer; + +/** Wrapper class for "AgentToServer" request body. */ +@AutoValue +public abstract class Request { + public abstract AgentToServer getAgentToServer(); + + public static Request create(AgentToServer agentToServer) { + return new AutoValue_Request(agentToServer); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/AcceptsDelaySuggestion.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/AcceptsDelaySuggestion.java new file mode 100644 index 000000000..f53b00d45 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/AcceptsDelaySuggestion.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import java.time.Duration; + +/** + * A {@link PeriodicDelay} implementation that wants to accept delay time suggestions, as explained + * here, + * must implement this interface. + */ +public interface AcceptsDelaySuggestion { + void suggestDelay(Duration delay); +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/FixedPeriodicDelay.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/FixedPeriodicDelay.java new file mode 100644 index 000000000..2b54180e5 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/FixedPeriodicDelay.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import java.time.Duration; + +final class FixedPeriodicDelay implements PeriodicDelay { + private final Duration duration; + + public FixedPeriodicDelay(Duration duration) { + this.duration = duration; + } + + @Override + public Duration getNextDelay() { + return duration; + } + + @Override + public void reset() {} +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/PeriodicDelay.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/PeriodicDelay.java new file mode 100644 index 000000000..67aa93491 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/PeriodicDelay.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import java.time.Duration; + +public interface PeriodicDelay { + static PeriodicDelay ofFixedDuration(Duration duration) { + return new FixedPeriodicDelay(duration); + } + + Duration getNextDelay(); + + void reset(); +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java new file mode 100644 index 000000000..32d5a60f1 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java @@ -0,0 +1,273 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import io.opentelemetry.opamp.client.internal.connectivity.http.HttpErrorException; +import io.opentelemetry.opamp.client.internal.connectivity.http.HttpSender; +import io.opentelemetry.opamp.client.internal.connectivity.http.RetryAfterParser; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; +import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseError; +import io.opentelemetry.opamp.client.internal.response.Response; +import java.io.IOException; +import java.time.Duration; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ThreadFactory; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import opamp.proto.AgentToServer; +import opamp.proto.ServerErrorResponse; +import opamp.proto.ServerErrorResponseType; +import opamp.proto.ServerToAgent; + +public final class HttpRequestService implements RequestService { + private final HttpSender requestSender; + // must be a single threaded executor, the code in this class relies on requests being processed + // serially + private final ScheduledExecutorService executorService; + private final AtomicBoolean isRunning = new AtomicBoolean(false); + private final AtomicBoolean hasStopped = new AtomicBoolean(false); + private final ConnectionStatus connectionStatus; + private final AtomicReference> scheduledTask = new AtomicReference<>(); + private final RetryAfterParser retryAfterParser; + @Nullable private Callback callback; + @Nullable private Supplier requestSupplier; + public static final PeriodicDelay DEFAULT_DELAY_BETWEEN_REQUESTS = + PeriodicDelay.ofFixedDuration(Duration.ofSeconds(30)); + + /** + * Creates an {@link HttpRequestService}. + * + * @param requestSender The HTTP sender implementation. + */ + public static HttpRequestService create(HttpSender requestSender) { + return create(requestSender, DEFAULT_DELAY_BETWEEN_REQUESTS, DEFAULT_DELAY_BETWEEN_REQUESTS); + } + + /** + * Creates an {@link HttpRequestService}. + * + * @param requestSender The HTTP sender implementation. + * @param periodicRequestDelay The time to wait between requests in general. + * @param periodicRetryDelay The time to wait between retries. + */ + public static HttpRequestService create( + HttpSender requestSender, + PeriodicDelay periodicRequestDelay, + PeriodicDelay periodicRetryDelay) { + return new HttpRequestService( + requestSender, + Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory()), + periodicRequestDelay, + periodicRetryDelay, + RetryAfterParser.getInstance()); + } + + HttpRequestService( + HttpSender requestSender, + ScheduledExecutorService executorService, + PeriodicDelay periodicRequestDelay, + PeriodicDelay periodicRetryDelay, + RetryAfterParser retryAfterParser) { + this.requestSender = requestSender; + this.executorService = executorService; + this.retryAfterParser = retryAfterParser; + this.connectionStatus = new ConnectionStatus(periodicRequestDelay, periodicRetryDelay); + } + + @Override + public void start(Callback callback, Supplier requestSupplier) { + if (hasStopped.get()) { + throw new IllegalStateException("HttpRequestService cannot start after it has been stopped."); + } + if (isRunning.compareAndSet(false, true)) { + this.callback = callback; + this.requestSupplier = requestSupplier; + scheduleNextExecution(); + } else { + throw new IllegalStateException("HttpRequestService is already running"); + } + } + + @Override + public void stop() { + if (isRunning.compareAndSet(true, false)) { + hasStopped.set(true); + executorService.shutdown(); + } + } + + @Override + public void sendRequest() { + if (!isRunning.get()) { + throw new IllegalStateException("HttpRequestService is not running"); + } + + executorService.execute( + () -> { + // cancel the already scheduled task, a new one is created after current request is + // processed + ScheduledFuture scheduledFuture = scheduledTask.get(); + if (scheduledFuture != null) { + scheduledFuture.cancel(false); + } + sendAndScheduleNext(); + }); + } + + private void sendAndScheduleNext() { + doSendRequest(); + scheduleNextExecution(); + } + + private void scheduleNextExecution() { + scheduledTask.set( + executorService.schedule( + this::sendAndScheduleNext, + connectionStatus.getNextDelay().toNanos(), + TimeUnit.NANOSECONDS)); + } + + private void doSendRequest() { + AgentToServer agentToServer = Objects.requireNonNull(requestSupplier).get().getAgentToServer(); + + byte[] data = agentToServer.encodeByteString().toByteArray(); + CompletableFuture future = + requestSender.send(outputStream -> outputStream.write(data), data.length); + try (HttpSender.Response response = future.get(30, TimeUnit.SECONDS)) { + getCallback().onConnectionSuccess(); + if (isSuccessful(response)) { + handleHttpSuccess( + Response.create(ServerToAgent.ADAPTER.decode(response.bodyInputStream()))); + } else { + handleHttpError(response); + } + } catch (IOException | InterruptedException | TimeoutException e) { + getCallback().onConnectionFailed(e); + } catch (ExecutionException e) { + if (e.getCause() != null) { + getCallback().onConnectionFailed(e.getCause()); + } else { + getCallback().onConnectionFailed(e); + } + } + } + + private void handleHttpError(HttpSender.Response response) { + int errorCode = response.statusCode(); + getCallback().onRequestFailed(new HttpErrorException(errorCode, response.statusMessage())); + + if (errorCode == 503 || errorCode == 429) { + String retryAfterHeader = response.getHeader("Retry-After"); + Duration retryAfter = null; + if (retryAfterHeader != null) { + Optional duration = retryAfterParser.tryParse(retryAfterHeader); + if (duration.isPresent()) { + retryAfter = duration.get(); + } + } + connectionStatus.retryAfter(retryAfter); + } + } + + private static boolean isSuccessful(HttpSender.Response response) { + return response.statusCode() >= 200 && response.statusCode() < 300; + } + + private void handleHttpSuccess(Response response) { + connectionStatus.success(); + ServerToAgent serverToAgent = response.getServerToAgent(); + + if (serverToAgent.error_response != null) { + handleErrorResponse(serverToAgent.error_response); + } else { + getCallback().onRequestSuccess(response); + } + } + + private void handleErrorResponse(ServerErrorResponse errorResponse) { + if (errorResponse.type.equals(ServerErrorResponseType.ServerErrorResponseType_Unavailable)) { + Duration retryAfter = null; + if (errorResponse.retry_info != null) { + retryAfter = Duration.ofNanos(errorResponse.retry_info.retry_after_nanoseconds); + } + connectionStatus.retryAfter(retryAfter); + } + getCallback().onRequestFailed(new OpampServerResponseError(errorResponse.error_message)); + } + + private Callback getCallback() { + return Objects.requireNonNull(callback); + } + + // this class is only used from a single threaded ScheduledExecutorService, hence no + // synchronization is needed + private static class ConnectionStatus { + private final PeriodicDelay periodicRequestDelay; + private final PeriodicDelay periodicRetryDelay; + + private boolean retrying; + private PeriodicDelay currentDelay; + + ConnectionStatus(PeriodicDelay periodicRequestDelay, PeriodicDelay periodicRetryDelay) { + this.periodicRequestDelay = periodicRequestDelay; + this.periodicRetryDelay = periodicRetryDelay; + currentDelay = periodicRequestDelay; + } + + void success() { + // after successful request transition from retry to regular delay + if (retrying) { + retrying = false; + periodicRequestDelay.reset(); + currentDelay = periodicRequestDelay; + } + } + + void retryAfter(@Nullable Duration retryAfter) { + // after failed request transition from regular to retry delay + if (!retrying) { + retrying = true; + periodicRetryDelay.reset(); + currentDelay = periodicRetryDelay; + if (retryAfter != null && periodicRetryDelay instanceof AcceptsDelaySuggestion) { + ((AcceptsDelaySuggestion) periodicRetryDelay).suggestDelay(retryAfter); + } + } + } + + Duration getNextDelay() { + return currentDelay.getNextDelay(); + } + } + + private static class DaemonThreadFactory implements ThreadFactory { + private final ThreadFactory delegate = Executors.defaultThreadFactory(); + + @Override + public Thread newThread(@Nonnull Runnable r) { + Thread t = delegate.newThread(r); + try { + t.setDaemon(true); + } catch (SecurityException e) { + // Well, we tried. + } + return t; + } + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java new file mode 100644 index 000000000..ee47e4249 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.response.Response; +import java.util.function.Supplier; + +/** + * Handles the network connectivity in general, its implementation can choose what protocol to use + * (HTTP or WebSocket) and should provide the necessary configurations options depending on the + * case. There are 2 implementations ready to use, {@link HttpRequestService}, for using HTTP, and + * {@link WebSocketRequestService} for using WebSocket. The {@link OpampClient} must not be aware of + * the specific implementation it uses as it can expect the same behavior from either. + */ +public interface RequestService { + + /** + * Starts the service. The actions done in this method depend on the implementation. For HTTP this + * is where the periodic poll task should get started, whereas for WebSocket this is where the + * connectivity is started. + * + * @param callback This is the only way that the service can communicate back to the {@link + * OpampClient} implementation. + * @param requestSupplier This supplier must be queried every time a new request is about to be + * sent. + */ + void start(Callback callback, Supplier requestSupplier); + + /** Triggers a new request send. */ + void sendRequest(); + + /** + * Clears the service for good. No further calls to {@link #sendRequest()} can be made after this + * method is called. + */ + void stop(); + + /** Allows the service to talk back to the {@link OpampClient} implementation. */ + interface Callback { + /** + * For WebSocket implementations, this is called when the connection is established. For HTTP + * implementations, this is called on every HTTP request that ends successfully. + */ + void onConnectionSuccess(); + + /** + * For WebSocket implementations, this is called when the connection cannot be made or is lost. + * For HTTP implementations, this is called on every HTTP request that cannot get a response. + * + * @param throwable The detailed error. + */ + void onConnectionFailed(Throwable throwable); + + /** + * For WebSocket implementations, this is called every time there's a new message from the + * server. For HTTP implementations, this is called when a successful HTTP request is finished + * with a valid server to agent response body. + * + * @param response The server to agent message. + */ + void onRequestSuccess(Response response); + + /** + * For both HTTP and WebSocket implementations, this is called when an attempt at sending a + * message fails. + * + * @param throwable The detailed error. + */ + void onRequestFailed(Throwable throwable); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java new file mode 100644 index 000000000..08ad6a4d2 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java @@ -0,0 +1,19 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.response; + +public class OpampServerResponseError extends Exception { + private static final long serialVersionUID = 1L; + + /** + * Constructs an OpAMP error related exception. + * + * @param message The OpAMP error message. + */ + public OpampServerResponseError(String message) { + super(message); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/Response.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/Response.java new file mode 100644 index 000000000..c9b6bc19e --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/Response.java @@ -0,0 +1,18 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.response; + +import com.google.auto.value.AutoValue; +import opamp.proto.ServerToAgent; + +@AutoValue +public abstract class Response { + public abstract ServerToAgent getServerToAgent(); + + public static Response create(ServerToAgent serverToAgent) { + return new AutoValue_Response(serverToAgent); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/tools/SystemTime.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/tools/SystemTime.java new file mode 100644 index 000000000..1d7f9e61e --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/tools/SystemTime.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.tools; + +/** Utility to be able to mock the current system time for testing purposes. */ +public final class SystemTime { + private static final SystemTime INSTANCE = new SystemTime(); + + public static SystemTime getInstance() { + return INSTANCE; + } + + private SystemTime() {} + + public long getCurrentTimeMillis() { + return System.currentTimeMillis(); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParserTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParserTest.java new file mode 100644 index 000000000..ece1ff0a7 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/http/RetryAfterParserTest.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.http; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import io.opentelemetry.opamp.client.internal.tools.SystemTime; +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class RetryAfterParserTest { + + @Test + void verifyParsing() { + SystemTime systemTime = mock(); + long currentTimeMillis = 1577836800000L; // Wed, 01 Jan 2020 00:00:00 GMT + when(systemTime.getCurrentTimeMillis()).thenReturn(currentTimeMillis); + + RetryAfterParser parser = new RetryAfterParser(systemTime); + + assertThat(parser.tryParse("123")).get().isEqualTo(Duration.ofSeconds(123)); + assertThat(parser.tryParse("Wed, 01 Jan 2020 01:00:00 GMT")) + .get() + .isEqualTo(Duration.ofHours(1)); + + // Check when provided time is older than the current one + assertThat(parser.tryParse("Tue, 31 Dec 2019 23:00:00 GMT")).isNotPresent(); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java new file mode 100644 index 000000000..63439e8c6 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java @@ -0,0 +1,492 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import io.opentelemetry.opamp.client.internal.connectivity.http.HttpErrorException; +import io.opentelemetry.opamp.client.internal.connectivity.http.HttpSender; +import io.opentelemetry.opamp.client.internal.connectivity.http.RetryAfterParser; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; +import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; +import io.opentelemetry.opamp.client.internal.response.Response; +import java.io.ByteArrayInputStream; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Queue; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.Delayed; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import opamp.proto.AgentToServer; +import opamp.proto.RetryInfo; +import opamp.proto.ServerErrorResponse; +import opamp.proto.ServerErrorResponseType; +import opamp.proto.ServerToAgent; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@SuppressWarnings("unchecked") +@ExtendWith(MockitoExtension.class) +class HttpRequestServiceTest { + + private static final Duration REGULAR_DELAY = Duration.ofSeconds(1); + private static final Duration RETRY_DELAY = Duration.ofSeconds(5); + + @Mock private RequestService.Callback callback; + private final List scheduledTasks = new ArrayList<>(); + private ScheduledExecutorService executorService; + private TestHttpSender requestSender; + private PeriodicDelay periodicRequestDelay; + private PeriodicDelayWithSuggestion periodicRetryDelay; + private int requestSize = -1; + private HttpRequestService httpRequestService; + + @BeforeEach + void setUp() { + requestSender = new TestHttpSender(); + periodicRequestDelay = createPeriodicDelay(REGULAR_DELAY); + periodicRetryDelay = createPeriodicDelayWithSuggestionSupport(RETRY_DELAY); + executorService = createTestScheduleExecutorService(); + httpRequestService = + new HttpRequestService( + requestSender, + executorService, + periodicRequestDelay, + periodicRetryDelay, + RetryAfterParser.getInstance()); + httpRequestService.start(callback, this::createRequestSupplier); + } + + @AfterEach + void tearDown() { + httpRequestService.stop(); + scheduledTasks.clear(); + verify(executorService).shutdown(); + } + + @Test + void verifyStart_scheduledFirstTask() { + assertThat(scheduledTasks).hasSize(1); + ScheduledTask firstTask = scheduledTasks.get(0); + assertThat(firstTask.delay).isEqualTo(REGULAR_DELAY); + + // Verify initial task creates next one + scheduledTasks.clear(); + requestSender.enqueueResponse(createSuccessfulResponse(new ServerToAgent.Builder().build())); + firstTask.run(); + + assertThat(scheduledTasks).hasSize(1); + + // Check on-demand requests don't create subsequent tasks + requestSender.enqueueResponse(createSuccessfulResponse(new ServerToAgent.Builder().build())); + httpRequestService.sendRequest(); + + assertThat(scheduledTasks).hasSize(1); + } + + @Test + void verifySendingRequest_happyPath() { + ServerToAgent serverToAgent = new ServerToAgent.Builder().build(); + HttpSender.Response httpResponse = createSuccessfulResponse(serverToAgent); + requestSender.enqueueResponse(httpResponse); + + httpRequestService.sendRequest(); + + verifySingleRequestSent(); + verifyRequestSuccessCallback(serverToAgent); + verify(callback).onConnectionSuccess(); + } + + @Test + void verifyWhenSendingOnDemandRequest_andDelayChanges() { + // Initial state + assertThat(assertAndGetSingleCurrentTask().delay).isEqualTo(REGULAR_DELAY); + + // Trigger delay strategy change + requestSender.enqueueResponse(createFailedResponse(503)); + httpRequestService.sendRequest(); + + // Expected state + assertThat(assertAndGetSingleCurrentTask().delay).isEqualTo(RETRY_DELAY); + } + + @Test + void verifySendingRequest_whenTheresAParsingError() { + HttpSender.Response httpResponse = createSuccessfulResponse(new byte[] {1, 2, 3}); + requestSender.enqueueResponse(httpResponse); + + httpRequestService.sendRequest(); + + verifySingleRequestSent(); + verify(callback).onConnectionFailed(any()); + } + + @Test + void verifySendingRequest_whenThereIsAnExecutionError() + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture future = mock(); + requestSender.enqueueResponseFuture(future); + Exception myException = mock(); + doThrow(new ExecutionException(myException)).when(future).get(30, TimeUnit.SECONDS); + + httpRequestService.sendRequest(); + + verifySingleRequestSent(); + verify(callback).onConnectionFailed(myException); + } + + @Test + void verifySendingRequest_whenThereIsAnInterruptedException() + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture future = mock(); + requestSender.enqueueResponseFuture(future); + InterruptedException myException = mock(); + doThrow(myException).when(future).get(30, TimeUnit.SECONDS); + + httpRequestService.sendRequest(); + + verifySingleRequestSent(); + verify(callback).onConnectionFailed(myException); + } + + @Test + void verifySendingRequest_whenThereIsAGenericHttpError() { + requestSender.enqueueResponse(createFailedResponse(500)); + + httpRequestService.sendRequest(); + + verifySingleRequestSent(); + verifyRequestFailedCallback(500); + } + + @Test + void verifySendingRequest_whenThereIsATooManyRequestsError() { + verifyRetryDelayOnError(createFailedResponse(429), RETRY_DELAY); + } + + @Test + void verifySendingRequest_whenThereIsATooManyRequestsError_withSuggestedDelay() { + HttpSender.Response response = createFailedResponse(429); + when(response.getHeader("Retry-After")).thenReturn("5"); + + verifyRetryDelayOnError(response, Duration.ofSeconds(5)); + } + + @Test + void verifySendingRequest_whenServerProvidesRetryInfo() { + long nanosecondsToWaitForRetry = 1000; + ServerErrorResponse errorResponse = + new ServerErrorResponse.Builder() + .type(ServerErrorResponseType.ServerErrorResponseType_Unavailable) + .retry_info( + new RetryInfo.Builder().retry_after_nanoseconds(nanosecondsToWaitForRetry).build()) + .build(); + ServerToAgent serverToAgent = new ServerToAgent.Builder().error_response(errorResponse).build(); + HttpSender.Response response = createSuccessfulResponse(serverToAgent); + + verifyRetryDelayOnError(response, Duration.ofNanos(nanosecondsToWaitForRetry)); + } + + @Test + void verifySendingRequest_whenServerIsUnavailable() { + ServerErrorResponse errorResponse = + new ServerErrorResponse.Builder() + .type(ServerErrorResponseType.ServerErrorResponseType_Unavailable) + .build(); + ServerToAgent serverToAgent = new ServerToAgent.Builder().error_response(errorResponse).build(); + HttpSender.Response response = createSuccessfulResponse(serverToAgent); + + verifyRetryDelayOnError(response, RETRY_DELAY); + } + + @Test + void verifySendingRequest_whenThereIsAServiceUnavailableError() { + verifyRetryDelayOnError(createFailedResponse(503), RETRY_DELAY); + } + + @Test + void verifySendingRequest_whenThereIsAServiceUnavailableError_withSuggestedDelay() { + HttpSender.Response response = createFailedResponse(503); + when(response.getHeader("Retry-After")).thenReturn("2"); + + verifyRetryDelayOnError(response, Duration.ofSeconds(2)); + } + + @Test + void verifySendingRequest_duringRegularMode() { + requestSender.enqueueResponse(createSuccessfulResponse(new ServerToAgent.Builder().build())); + + httpRequestService.sendRequest(); + + verifySingleRequestSent(); + } + + private void verifyRetryDelayOnError( + HttpSender.Response errorResponse, Duration expectedRetryDelay) { + requestSender.enqueueResponse(errorResponse); + ScheduledTask previousTask = assertAndGetSingleCurrentTask(); + + previousTask.run(); + + verifySingleRequestSent(); + verify(periodicRetryDelay).reset(); + verify(callback).onRequestFailed(any()); + ScheduledTask retryTask = assertAndGetSingleCurrentTask(); + assertThat(retryTask.delay).isEqualTo(expectedRetryDelay); + + // Retry with another error + clearInvocations(callback); + scheduledTasks.clear(); + requestSender.enqueueResponse(createFailedResponse(500)); + retryTask.run(); + + verifySingleRequestSent(); + verify(callback).onRequestFailed(any()); + ScheduledTask retryTask2 = assertAndGetSingleCurrentTask(); + assertThat(retryTask2.delay).isEqualTo(expectedRetryDelay); + + // Retry with a success + clearInvocations(callback); + scheduledTasks.clear(); + ServerToAgent serverToAgent = new ServerToAgent.Builder().build(); + requestSender.enqueueResponse(createSuccessfulResponse(serverToAgent)); + retryTask2.run(); + + verify(periodicRequestDelay).reset(); + verifySingleRequestSent(); + verifyRequestSuccessCallback(serverToAgent); + assertThat(assertAndGetSingleCurrentTask().delay).isEqualTo(REGULAR_DELAY); + } + + private Request createRequestSupplier() { + AgentToServer agentToServer = new AgentToServer.Builder().sequence_num(10).build(); + requestSize = agentToServer.encodeByteString().size(); + return Request.create(agentToServer); + } + + private ScheduledTask assertAndGetSingleCurrentTask() { + assertThat(scheduledTasks).hasSize(1); + return scheduledTasks.get(0); + } + + private void verifySingleRequestSent() { + List requests = requestSender.getRequests(1); + assertThat(requests.get(0).contentLength).isEqualTo(requestSize); + } + + private void verifyRequestSuccessCallback(ServerToAgent serverToAgent) { + verify(callback).onRequestSuccess(Response.create(serverToAgent)); + } + + private void verifyRequestFailedCallback(int errorCode) { + ArgumentCaptor captor = ArgumentCaptor.forClass(HttpErrorException.class); + verify(callback).onRequestFailed(captor.capture()); + assertThat(captor.getValue().getErrorCode()).isEqualTo(errorCode); + assertThat(captor.getValue().getMessage()).isEqualTo("Error message"); + } + + private ScheduledExecutorService createTestScheduleExecutorService() { + ScheduledExecutorService service = mock(); + + lenient() + .doAnswer( + invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }) + .when(service) + .execute(any()); + + when(service.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) + .thenAnswer( + invocation -> { + ScheduledTask task = + new ScheduledTask(invocation.getArgument(0), invocation.getArgument(1)); + + scheduledTasks.add(task); + + return task; + }); + + return service; + } + + private static HttpSender.Response createSuccessfulResponse(ServerToAgent serverToAgent) { + return createSuccessfulResponse(serverToAgent.encodeByteString().toByteArray()); + } + + private static HttpSender.Response createSuccessfulResponse(byte[] serverToAgent) { + HttpSender.Response response = mock(); + ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(serverToAgent); + when(response.statusCode()).thenReturn(200); + when(response.bodyInputStream()).thenReturn(byteArrayInputStream); + return response; + } + + private static HttpSender.Response createFailedResponse(int statusCode) { + HttpSender.Response response = mock(); + when(response.statusCode()).thenReturn(statusCode); + when(response.statusMessage()).thenReturn("Error message"); + return response; + } + + private static PeriodicDelay createPeriodicDelay(Duration delay) { + PeriodicDelay mock = mock(); + when(mock.getNextDelay()).thenReturn(delay); + return mock; + } + + private static PeriodicDelayWithSuggestion createPeriodicDelayWithSuggestionSupport( + Duration delay) { + return spy(new PeriodicDelayWithSuggestion(delay)); + } + + private static class PeriodicDelayWithSuggestion + implements PeriodicDelay, AcceptsDelaySuggestion { + private final Duration initialDelay; + private Duration currentDelay; + + private PeriodicDelayWithSuggestion(Duration initialDelay) { + this.initialDelay = initialDelay; + currentDelay = initialDelay; + } + + @Override + public void suggestDelay(Duration delay) { + currentDelay = delay; + } + + @Override + public Duration getNextDelay() { + return currentDelay; + } + + @Override + public void reset() { + currentDelay = initialDelay; + } + } + + private static class TestHttpSender implements HttpSender { + private final List requests = new ArrayList<>(); + + @SuppressWarnings("JdkObsolete") + private final Queue> responses = new LinkedList<>(); + + @Override + public CompletableFuture send(BodyWriter writer, int contentLength) { + requests.add(new RequestParams(contentLength)); + CompletableFuture response = null; + try { + response = responses.remove(); + } catch (NoSuchElementException e) { + fail("Unwanted triggered request"); + } + return response; + } + + public void enqueueResponse(HttpSender.Response response) { + enqueueResponseFuture(CompletableFuture.completedFuture(response)); + } + + public void enqueueResponseFuture(CompletableFuture future) { + responses.add(future); + } + + public List getRequests(int size) { + assertThat(requests).hasSize(size); + List immutableRequests = + Collections.unmodifiableList(new ArrayList<>(requests)); + requests.clear(); + return immutableRequests; + } + + private static class RequestParams { + public final int contentLength; + + private RequestParams(int contentLength) { + this.contentLength = contentLength; + } + } + } + + private class ScheduledTask implements ScheduledFuture { + private final Runnable runnable; + private final Duration delay; + + public void run() { + get(); + } + + private ScheduledTask(Runnable runnable, long timeNanos) { + this.runnable = runnable; + this.delay = Duration.ofNanos(timeNanos); + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return scheduledTasks.remove(this); + } + + @Override + public boolean isCancelled() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDone() { + throw new UnsupportedOperationException(); + } + + @Override + public Object get() { + scheduledTasks.remove(this); + runnable.run(); + return null; + } + + @Override + public Object get(long timeout, @NotNull TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(@NotNull Delayed o) { + throw new UnsupportedOperationException(); + } + } +} From 0d0f5133489bc0fc94717e770891b51d3039d25e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 17 Jun 2025 08:39:39 +0300 Subject: [PATCH 085/371] fix(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.6 (#1965) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- runtime-attach/runtime-attach-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-attach/runtime-attach-core/build.gradle.kts b/runtime-attach/runtime-attach-core/build.gradle.kts index 445224d32..835454e6c 100644 --- a/runtime-attach/runtime-attach-core/build.gradle.kts +++ b/runtime-attach/runtime-attach-core/build.gradle.kts @@ -7,7 +7,7 @@ description = "To help in create an OpenTelemetry distro able to runtime attach otelJava.moduleName.set("io.opentelemetry.contrib.attach.core") dependencies { - implementation("net.bytebuddy:byte-buddy-agent:1.17.5") + implementation("net.bytebuddy:byte-buddy-agent:1.17.6") // Used by byte-buddy but not brought in as a transitive dependency. compileOnly("com.google.code.findbugs:annotations") From b9be5bdd626a18e87f022fa4422eb630df9e3606 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 19 Jun 2025 08:16:30 +0300 Subject: [PATCH 086/371] fix(deps): update all patch versions to v1.21.2 (#1966) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- dependencyManagement/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 59439264e..e8764a42a 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -136,7 +136,7 @@ testing { implementation(project(project.path)) implementation(enforcedPlatform("org.junit:junit-bom:5.13.1")) - implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.1")) + implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.2")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 6a496980c..e27155864 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { api("org.junit-pioneer:junit-pioneer:1.9.1") api("org.skyscreamer:jsonassert:1.5.3") api("org.apache.kafka:kafka-clients:4.0.0") - api("org.testcontainers:kafka:1.21.1") + api("org.testcontainers:kafka:1.21.2") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") api("tools.profiler:async-profiler:4.0") From 70d2c6520acb938235cf2cddcf601d9e861ecce1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 09:22:15 +0300 Subject: [PATCH 087/371] fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.2 (#1967) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index 89d7ef490..6f954c7db 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.0 + 3.5.2 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index fe1e2e1e1..84dedd9c8 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.0 + 3.5.2 io.opentelemetry.contrib.maven.test From 37f0278c849184f3b96622847634abedd89c578e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 20 Jun 2025 10:22:36 +0300 Subject: [PATCH 088/371] fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.3 (#1968) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index 6f954c7db..15f2d5e25 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.2 + 3.5.3 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index 84dedd9c8..750e3605b 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.2 + 3.5.3 io.opentelemetry.contrib.maven.test From da7268339a824223feef3180cd7bd4e230e602e3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 23 Jun 2025 08:37:03 -0700 Subject: [PATCH 089/371] fix(deps): update all patch versions (#1972) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- maven-extension/src/test/resources/projects/jib_1/pom.xml | 2 +- maven-extension/src/test/resources/projects/jib_2/pom.xml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index c1b9fe742..fd95a653f 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.37.0") + implementation("com.google.auth:google-auth-library-oauth2-http:1.37.1") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") diff --git a/maven-extension/src/test/resources/projects/jib_1/pom.xml b/maven-extension/src/test/resources/projects/jib_1/pom.xml index 36471bc43..d59d51bcc 100644 --- a/maven-extension/src/test/resources/projects/jib_1/pom.xml +++ b/maven-extension/src/test/resources/projects/jib_1/pom.xml @@ -15,7 +15,7 @@ com.google.cloud.tools jib-maven-plugin - 3.4.5 + 3.4.6 docker.io/john/${project.artifactId}:latest diff --git a/maven-extension/src/test/resources/projects/jib_2/pom.xml b/maven-extension/src/test/resources/projects/jib_2/pom.xml index b5bf39fc0..9c34e16df 100644 --- a/maven-extension/src/test/resources/projects/jib_2/pom.xml +++ b/maven-extension/src/test/resources/projects/jib_2/pom.xml @@ -15,7 +15,7 @@ com.google.cloud.tools jib-maven-plugin - 3.4.5 + 3.4.6 gcr.io/my-gcp-project/my-app From 0b7c5de951aeae8548d49d6f732cf65b50f6965d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Jun 2025 09:36:26 -0700 Subject: [PATCH 090/371] Fix OSSF scorecard branch protection check (#1971) --- .github/workflows/ossf-scorecard.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index cff5b894f..033e6cc7c 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,8 +23,18 @@ jobs: with: persist-credentials: false + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + id: create-token + with: + # analyzing classic branch protections requires a token with admin read permissions + # see https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md + # and https://github.com/open-telemetry/community/issues/2769 + app-id: ${{ vars.OSSF_SCORECARD_APP_ID }} + private-key: ${{ secrets.OSSF_SCORECARD_PRIVATE_KEY }} + - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: + repo_token: ${{ steps.create-token.outputs.token }} results_file: results.sarif results_format: sarif publish_results: true From b3a3b2cd17e2d2d30589872bb535ede9029844ec Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Jun 2025 09:36:53 -0700 Subject: [PATCH 091/371] Revert "Update version to 1.48.0-SNAPSHOT" (#1970) --- CHANGELOG.md | 2 -- version.gradle.kts | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f38cda1f..9351011ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ ## Unreleased -## Version 1.47.0 (2025-05-19) - ### GCP authentication extension - Update the internal implementation such that the required headers are retrieved diff --git a/version.gradle.kts b/version.gradle.kts index 4d162323b..0ae7508cb 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.48.0-SNAPSHOT" -val alphaVersion = "1.48.0-alpha-SNAPSHOT" +val stableVersion = "1.47.0-SNAPSHOT" +val alphaVersion = "1.47.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From 9b6fa69bdf2777ddf51703f0512de331db433f9e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 23 Jun 2025 13:52:12 -0700 Subject: [PATCH 092/371] Revert "Fix OSSF scorecard branch protection check" (#1973) --- .github/workflows/ossf-scorecard.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 033e6cc7c..cff5b894f 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,18 +23,8 @@ jobs: with: persist-credentials: false - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 - id: create-token - with: - # analyzing classic branch protections requires a token with admin read permissions - # see https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md - # and https://github.com/open-telemetry/community/issues/2769 - app-id: ${{ vars.OSSF_SCORECARD_APP_ID }} - private-key: ${{ secrets.OSSF_SCORECARD_PRIVATE_KEY }} - - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: - repo_token: ${{ steps.create-token.outputs.token }} results_file: results.sarif results_format: sarif publish_results: true From 4b43cb610dc251114c3bb2ad7b5c324d950976cf Mon Sep 17 00:00:00 2001 From: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:26:26 -0700 Subject: [PATCH 093/371] Update community member listings (#1976) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- README.md | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 33eb10f34..099db9724 100644 --- a/README.md +++ b/README.md @@ -72,31 +72,37 @@ domain we would be very interested in supporting it. Please suggestion. PRs are always welcome and greatly appreciated, but for larger functional changes a pre-coding introduction can be helpful to ensure this is the correct place and that active or conflicting efforts don't exist. -Triagers ([@open-telemetry/java-contrib-triagers](https://github.com/orgs/open-telemetry/teams/java-contrib-triagers)): +### Maintainers -- All [component owners](https://github.com/open-telemetry/opentelemetry-java-contrib/blob/main/.github.amrom.workers.devponent_owners.yml) are given Triager permissions to this repository. +- [Jack Berg](https://github.com/jack-berg), New Relic +- [Jason Plumb](https://github.com/breedx-splk), Splunk +- [Lauri Tulmin](https://github.com/laurit), Splunk +- [Trask Stalnaker](https://github.com/trask), Microsoft + +For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer). -Approvers ([@open-telemetry/java-contrib-approvers](https://github.com/orgs/open-telemetry/teams/java-contrib-approvers)): +### Approvers - [John Watson](https://github.com/jkwatson), Verta.ai -Maintainers ([@open-telemetry/java-contrib-maintainers](https://github.com/orgs/open-telemetry/teams/java-contrib-maintainers)): +For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver). -- [Jack Berg](https://github.com/jack-berg), New Relic -- [Jason Plumb](https://github.com/breedx-splk), Splunk -- [Lauri Tulmin](https://github.com/laurit), Splunk -- [Trask Stalnaker](https://github.com/trask), Microsoft +### Triagers + +- All [component owners](https://github.com/open-telemetry/opentelemetry-java-contrib/blob/main/.github.amrom.workers.devponent_owners.yml) are given Triager permissions to this repository. + +For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#triagers). -Emeritus maintainers: +### Emeritus maintainers - [Mateusz Rzeszutek](https://github.com/mateuszrzeszutek) - [Nikita Salnikov-Tarnovski](https://github.com/iNikem) - [Ryan Fitzpatrick](https://github.com/rmfitzpatrick) -Learn more about roles in the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md). +For more information about the emeritus role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#emeritus-maintainerapprovertriager). -Thanks to all the people who already contributed! +### Thanks to all of our contributors! - + Repo contributors From fc0fcb8dfaf6e2cc0ddcd16cd8966ecd20d6ef7d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Jun 2025 20:45:22 -0700 Subject: [PATCH 094/371] fix(deps): update dependency org.junit:junit-bom to v5.13.2 (#1977) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index e8764a42a..c98d60d7b 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -135,7 +135,7 @@ testing { dependencies { implementation(project(project.path)) - implementation(enforcedPlatform("org.junit:junit-bom:5.13.1")) + implementation(enforcedPlatform("org.junit:junit-bom:5.13.2")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.2")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) From 032e0e67ccb9107100db9047a6c7767f98b2f8c5 Mon Sep 17 00:00:00 2001 From: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Date: Thu, 26 Jun 2025 16:08:13 -0700 Subject: [PATCH 095/371] Fix outdated community membership link (#1978) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 099db9724..29bc8d222 100644 --- a/README.md +++ b/README.md @@ -79,19 +79,19 @@ can be helpful to ensure this is the correct place and that active or conflictin - [Lauri Tulmin](https://github.com/laurit), Splunk - [Trask Stalnaker](https://github.com/trask), Microsoft -For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#maintainer). +For more information about the maintainer role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#maintainer). ### Approvers - [John Watson](https://github.com/jkwatson), Verta.ai -For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#approver). +For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver). ### Triagers - All [component owners](https://github.com/open-telemetry/opentelemetry-java-contrib/blob/main/.github.amrom.workers.devponent_owners.yml) are given Triager permissions to this repository. -For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/community-membership.md#triagers). +For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager). ### Emeritus maintainers From 3c1269ef4eb633aa0b5fd6607308b9f0a7a4fac7 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sun, 29 Jun 2025 02:04:03 +0200 Subject: [PATCH 096/371] add test for Mac OS and Windows (#1934) Co-authored-by: Trask Stalnaker Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/workflows/build.yml | 39 +++++++++++++------ .../internal/storage/FolderManager.java | 13 ++++++- .../disk/buffering/IntegrationTest.java | 6 +++ .../internal/storage/FolderManagerTest.java | 6 +++ .../internal/storage/StorageTest.java | 6 +++ .../storage/files/ReadableFileTest.java | 6 +++ .../storage/files/WritableFileTest.java | 6 +++ .../InferredSpansAutoConfigTest.java | 4 +- .../contrib/jmxscraper/JmxScraperTest.java | 4 ++ opamp-client/build.gradle.kts | 34 ++++++++-------- 10 files changed, 92 insertions(+), 32 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 77c3b86fc..4850fa48c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,29 +36,46 @@ jobs: run: ./gradlew build -x test test: - name: test (${{ matrix.test-java-version }}) - runs-on: ubuntu-latest + name: Test + runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: + os: + - macos-latest + - macos-13 + - ubuntu-latest + - windows-latest test-java-version: - 8 - 11 - 17 - 21 - 23 - fail-fast: false + # macos-latest drops support for java 8 temurin. Run java 8 on macos-13. Run java 11, 17, 21 on macos-latest. + exclude: + - os: macos-latest + test-java-version: 8 + - os: macos-13 + test-java-version: 11 + - os: macos-13 + test-java-version: 17 + - os: macos-13 + test-java-version: 21 + - os: macos-13 + test-java-version: 23 steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - id: setup-test-java - name: Set up JDK ${{ matrix.test-java-version }} for running tests + - id: setup-java-test + name: Set up Java ${{ matrix.test-java-version }} for tests uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: - # using zulu because new releases get published quickly - distribution: zulu + distribution: temurin java-version: ${{ matrix.test-java-version }} - - name: Set up JDK for running Gradle + - id: setup-java + name: Set up Java for build uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 with: distribution: temurin @@ -71,9 +88,9 @@ jobs: - name: Gradle test run: > ./gradlew test - -PtestJavaVersion=${{ matrix.test-java-version }} - -Porg.gradle.java.installations.paths=${{ steps.setup-test-java.outputs.path }} - -Porg.gradle.java.installations.auto-download=false + "-PtestJavaVersion=${{ matrix.test-java-version }}" + "-Porg.gradle.java.installations.paths=${{ steps.setup-java-test.outputs.path }}" + "-Porg.gradle.java.installations.auto-download=false" integration-test: runs-on: ubuntu-latest diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java index 0f3ad7ad7..1f76419ab 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java @@ -11,13 +11,14 @@ import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; import io.opentelemetry.sdk.common.Clock; +import java.io.Closeable; import java.io.File; import java.io.IOException; import java.util.Objects; import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; -public final class FolderManager { +public final class FolderManager implements Closeable { private final File folder; private final Clock clock; private final StorageConfiguration configuration; @@ -30,6 +31,16 @@ public FolderManager(File folder, StorageConfiguration configuration, Clock cloc this.clock = clock; } + @Override + public void close() throws IOException { + if (currentReadableFile != null) { + currentReadableFile.close(); + } + if (currentWritableFile != null) { + currentWritableFile.close(); + } + } + @Nullable public synchronized ReadableFile getReadableFile() throws IOException { currentReadableFile = null; diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java index b2da05c24..b46cba12f 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java @@ -48,6 +48,7 @@ import java.util.function.Function; import java.util.function.Supplier; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -103,6 +104,11 @@ void setUp() throws IOException { logger = createLoggerProvider(logToDiskExporter).get("LogInstrumentationScope"); } + @AfterEach + void tearDown() throws IOException { + spanStorage.close(); + } + @NotNull private ToDiskExporter buildToDiskExporter( SignalSerializer serializer, Function, CompletableResultCode> exporter) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java index d2b73f13d..5b72c29e8 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java @@ -24,6 +24,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -40,6 +41,11 @@ void setUp() { folderManager = new FolderManager(rootDir, TestData.getConfiguration(rootDir), clock); } + @AfterEach + void tearDown() throws Exception { + folderManager.close(); + } + @Test void createWritableFile_withTimeMillisAsName() throws IOException { when(clock.now()).thenReturn(MILLISECONDS.toNanos(1000L)); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java index 9accaefff..7f134afdc 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java @@ -26,6 +26,7 @@ import java.io.File; import java.io.IOException; import java.util.function.Function; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,6 +48,11 @@ void setUp() throws IOException { storage = new Storage(folderManager, true); } + @AfterEach + void tearDown() throws IOException { + storage.close(); + } + @Test void whenReadingAndProcessingSuccessfully_returnSuccess() throws IOException { when(folderManager.getReadableFile()).thenReturn(readableFile); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java index dc608e733..a94b5fb3d 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java @@ -32,6 +32,7 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -98,6 +99,11 @@ void setUp() throws IOException { readableFile = new ReadableFile(source, CREATED_TIME_MILLIS, clock, getConfiguration(dir)); } + @AfterEach + void tearDown() throws IOException { + readableFile.close(); + } + private static void addFileContents(File source) throws IOException { List items = new ArrayList<>(); items.add(SERIALIZER.serialize(Collections.singleton(FIRST_LOG_RECORD))); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java index 6830b471a..3df37eb4c 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java @@ -22,6 +22,7 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; @@ -47,6 +48,11 @@ void setUp() throws IOException { clock); } + @AfterEach + void tearDown() throws IOException { + writableFile.close(); + } + @Test void hasNotExpired_whenWriteAgeHasNotExpired() { when(clock.now()).thenReturn(MILLISECONDS.toNanos(1500L)); diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java index 1b6a7e3e2..76a6333ba 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java @@ -7,6 +7,7 @@ import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.junit.jupiter.api.condition.OS.WINDOWS; import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.OpenTelemetry; @@ -31,9 +32,9 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.condition.DisabledOnOs; -import org.junit.jupiter.api.condition.OS; import org.junit.jupiter.api.io.TempDir; +@DisabledOnOs(WINDOWS) // Uses async-profiler, which is not supported on Windows public class InferredSpansAutoConfigTest { @BeforeEach @@ -107,7 +108,6 @@ public void checkDisabledbyDefault() { } @DisabledOnOpenJ9 - @DisabledOnOs(OS.WINDOWS) @Test public void checkProfilerWorking() { try (AutoConfigTestProperties props = diff --git a/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java b/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java index 2d3178072..b51144360 100644 --- a/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java +++ b/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java @@ -17,6 +17,8 @@ import java.util.List; import java.util.Properties; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; import org.junitpioneer.jupiter.ClearSystemProperty; class JmxScraperTest { @@ -46,8 +48,10 @@ private static void testInvalidArguments(String... args) { } @Test + @DisabledOnOs(OS.WINDOWS) void shouldCreateConfig_propertiesLoadedFromFile() throws InvalidArgumentException { // Given + // Windows returns /C:/path/to/file, which is not a valid path for Path.get() in Java. String filePath = ClassLoader.getSystemClassLoader().getResource("validConfig.properties").getPath(); List args = Arrays.asList("-config", filePath); diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 0e33a87a6..f3ad4cfda 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -1,6 +1,6 @@ -import de.undercouch.gradle.tasks.download.Download import de.undercouch.gradle.tasks.download.DownloadExtension -import groovy.json.JsonSlurper +import java.net.HttpURLConnection +import java.net.URL plugins { id("otel.java-conventions") @@ -18,19 +18,9 @@ dependencies { testImplementation("org.mockito:mockito-inline") } -val opampReleaseInfo = tasks.register("opampLastReleaseInfo") { - group = "opamp" - src("https://api.github.com/repos/open-telemetry/opamp-spec/releases/latest") - dest(project.layout.buildDirectory.file("opamp/release.json")) -} - val opampProtos = tasks.register("opampProtoDownload", download) opampProtos.configure { group = "opamp" - dependsOn(opampReleaseInfo) - lastReleaseInfoJson.set { - opampReleaseInfo.get().dest - } outputProtosDir.set(project.layout.buildDirectory.dir("opamp/protos")) downloadedZipFile.set(project.layout.buildDirectory.file("intermediate/$name/release.zip")) } @@ -48,20 +38,28 @@ abstract class DownloadOpampProtos @Inject constructor( private val fileOps: FileSystemOperations, ) : DefaultTask() { - @get:InputFile - abstract val lastReleaseInfoJson: RegularFileProperty - @get:OutputDirectory abstract val outputProtosDir: DirectoryProperty @get:Internal abstract val downloadedZipFile: RegularFileProperty - @Suppress("UNCHECKED_CAST") @TaskAction fun execute() { - val releaseInfo = JsonSlurper().parse(lastReleaseInfoJson.get().asFile) as Map - val zipUrl = releaseInfo["zipball_url"] + // Get the latest release tag by following the redirect from GitHub's latest release URL + val latestReleaseUrl = "https://github.com/open-telemetry/opamp-spec/releases/latest" + val connection = URL(latestReleaseUrl).openConnection() as HttpURLConnection + connection.instanceFollowRedirects = false + connection.requestMethod = "HEAD" + + val redirectLocation = connection.getHeaderField("Location") + connection.disconnect() + + // Extract tag from URL like: https://github.com/open-telemetry/opamp-spec/releases/tag/v0.12.0 + val latestTag = redirectLocation.substringAfterLast("/") + // Download the source code for the latest release + val zipUrl = "https://github.com/open-telemetry/opamp-spec/zipball/$latestTag" + download.run { src(zipUrl) dest(downloadedZipFile) From 9ec6ff75406cac7c89d28220c642f88344526a50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 20:36:13 -0700 Subject: [PATCH 097/371] fix(deps): update errorprone packages to v2.39.0 (#1985) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index e27155864..5b4d66b91 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -25,8 +25,8 @@ dependencies { api("com.google.auto.service:auto-service-annotations:1.1.1") api("com.google.auto.value:auto-value:1.11.0") api("com.google.auto.value:auto-value-annotations:1.11.0") - api("com.google.errorprone:error_prone_annotations:2.38.0") - api("com.google.errorprone:error_prone_core:2.38.0") + api("com.google.errorprone:error_prone_annotations:2.39.0") + api("com.google.errorprone:error_prone_core:2.39.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") api("io.prometheus:simpleclient:0.16.0") From c6e20dba85bf7849c7258bb5df300fb78b3e41ef Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 03:40:05 +0000 Subject: [PATCH 098/371] fix(deps): update all patch versions (#1981) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- dependencyManagement/build.gradle.kts | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9b8e7cbb9..65fc424cd 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL - uses: github/codeql-action/init@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/init@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/analyze@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index cff5b894f..f8e80fc92 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@ce28f5bb42b7a9f2c824e633a3f6ee835bab6858 # v3.29.0 + uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 with: sarif_file: results.sarif diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index c98d60d7b..e33241437 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -136,7 +136,7 @@ testing { implementation(project(project.path)) implementation(enforcedPlatform("org.junit:junit-bom:5.13.2")) - implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.2")) + implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 5b4d66b91..8a78becf5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { api("org.junit-pioneer:junit-pioneer:1.9.1") api("org.skyscreamer:jsonassert:1.5.3") api("org.apache.kafka:kafka-clients:4.0.0") - api("org.testcontainers:kafka:1.21.2") + api("org.testcontainers:kafka:1.21.3") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") api("tools.profiler:async-profiler:4.0") From 8bf35d0894029707720c2cdd8720a7068d874166 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 30 Jun 2025 04:15:07 +0000 Subject: [PATCH 099/371] fix(deps): update dependency net.ltgt.gradle:gradle-errorprone-plugin to v4.3.0 (#1984) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 0b6823fc8..1ab367234 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -13,7 +13,7 @@ repositories { dependencies { // When updating, update above in plugins too implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.4") - implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.2.0") + implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.3") } From a8338679e7d3e74e5aa7a5845198695aa423929e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 29 Jun 2025 21:30:28 -0700 Subject: [PATCH 100/371] fix(deps): update dependency com.google.cloud.opentelemetry:detector-resources-support to v0.36.0 (#1982) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-resources/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-resources/build.gradle.kts b/gcp-resources/build.gradle.kts index 2a88c60b5..998c15870 100644 --- a/gcp-resources/build.gradle.kts +++ b/gcp-resources/build.gradle.kts @@ -12,7 +12,7 @@ dependencies { api("io.opentelemetry:opentelemetry-sdk") // Provides GCP resource detection support - implementation("com.google.cloud.opentelemetry:detector-resources-support:0.35.0") + implementation("com.google.cloud.opentelemetry:detector-resources-support:0.36.0") testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") From 88f07b6afde0b448a317a681cb2f125b38c0e502 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 30 Jun 2025 22:15:57 +0200 Subject: [PATCH 101/371] jmx scraper: add missing relevant config options (#1979) Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- jmx-scraper/README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 2f19ceab9..0cfd03306 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -85,12 +85,14 @@ Setting the value of `otel.jmx.target.source` allows to fit the following use-ca - `legacy` allows to keep using definitions that are very close to JMX Gatherer, this is the recommended option if preserving compatibility is required. Those definitions are in maintenance and are unlikely to evolve over time. - `instrumentation` forces using metrics definitions from instrumentation, hence only the reference. Metrics definitions and supported values of `otel.jmx.target.system` will be updated whenever the dependency on instrumentation is updated. -The following SDK configuration options are also relevant +The following [SDK configuration options](https://opentelemetry.io/docs/languages/java/configuration/#environment-variables-and-system-properties) are also relevant | config option | default value | description | |-------------------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| | `otel.metric.export.interval` | `1m` (1 minute) | metric export interval, also controls the JMX sampling interval | | `otel.metrics.exporter` | `otlp` | comma-separated list of metrics exporters supported values are `otlp` and `logging`, additional values might be provided through extra libraries in the classpath | +| `otel.service.name` | | service name | +| `otel.resource.attributes` | | used to specify otel resource attributes, including service attributes. See [the sdk configuration](https://opentelemetry.io/docs/languages/java/configuration/#properties-resource) and [service attributes](https://opentelemetry.io/docs/specs/semconv/registry/attributes/service/) | In addition to OpenTelemetry configuration, the following Java system properties can be provided through the command-line arguments, properties file or stdin and will be propagated to the JVM system properties: From c2ad96df7adb801753c5a465956871ee7420c6a6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 1 Jul 2025 07:28:54 -0700 Subject: [PATCH 102/371] chore(deps): update github/codeql-action action to v3.29.2 (#1987) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 65fc424cd..dfbca6b06 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL - uses: github/codeql-action/init@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index f8e80fc92..cfdb7ca62 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -42,6 +42,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@39edc492dbe16b1465b0cafca41432d857bdb31a # v3.29.1 + uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 with: sarif_file: results.sarif From 52c1ac978b84397980578df7f10b77dded8782cd Mon Sep 17 00:00:00 2001 From: Robert Elliot Date: Tue, 1 Jul 2025 19:26:38 +0100 Subject: [PATCH 103/371] fix: Image parsing (#1829) Signed-off-by: Robert Elliot --- .../io/opentelemetry/contrib/aws/resource/EcsResource.java | 2 +- .../src/test/resources/ecs-container-metadata-v3.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java index de6d50afe..efb112de8 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java @@ -314,7 +314,7 @@ private static class DockerImage { private static final Pattern imagePattern = Pattern.compile( - "^(?([^/\\s]+/)?([^:\\s]+))(:(?[^@\\s]+))?(@sha256:(?\\d+))?$"); + "^(?([^/\\s]+/)?([^:\\s]+))(:(?[^@\\s]+))?(@sha256:(?[\\da-fA-F]+))?$"); final String repository; final String tag; diff --git a/aws-resources/src/test/resources/ecs-container-metadata-v3.json b/aws-resources/src/test/resources/ecs-container-metadata-v3.json index 2e89ffe59..f7cc0f53f 100644 --- a/aws-resources/src/test/resources/ecs-container-metadata-v3.json +++ b/aws-resources/src/test/resources/ecs-container-metadata-v3.json @@ -2,7 +2,7 @@ "DockerId": "43481a6ce4842eec8fe72fc28500c6b52edcc0917f105b83379f88cac1ff3946", "Name": "nginx-curl", "DockerName": "ecs-nginx-5-nginx-curl-ccccb9f49db0dfe0d901", - "Image": "nrdlngr/nginx-curl", + "Image": "nrdlngr/nginx-curl:latest@sha256:8dc35e9386b5d280d285ae7a78d271a5d4a82106cb254fbed5fde4923faa8deb", "ImageID": "sha256:2e00ae64383cfc865ba0a2ba37f61b50a120d2d9378559dcd458dc0de47bc165", "Labels": { "com.amazonaws.ecs.cluster": "default", @@ -28,4 +28,4 @@ ] } ] -} \ No newline at end of file +} From 5ae1d570ec76faa39046852abc8c0ce86a1cae62 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Tue, 1 Jul 2025 11:27:24 -0700 Subject: [PATCH 104/371] Make link check consistent (#1986) --- .github/scripts/dependencies.dockerfile | 3 ++ .github/scripts/link-check.sh | 35 +++++++++++++++++++ .github/workflows/build.yml | 10 ++++-- .github/workflows/reusable-link-check.yml | 18 ++++++++++ .../reusable-markdown-link-check.yml | 23 ------------ .lychee.toml | 16 +++++++++ 6 files changed, 79 insertions(+), 26 deletions(-) create mode 100644 .github/scripts/dependencies.dockerfile create mode 100755 .github/scripts/link-check.sh create mode 100644 .github/workflows/reusable-link-check.yml delete mode 100644 .github/workflows/reusable-markdown-link-check.yml create mode 100644 .lychee.toml diff --git a/.github/scripts/dependencies.dockerfile b/.github/scripts/dependencies.dockerfile new file mode 100644 index 000000000..d8e3bbfe1 --- /dev/null +++ b/.github/scripts/dependencies.dockerfile @@ -0,0 +1,3 @@ +# this file exists so that Renovate can auto-update docker image versions that are then used elsewhere + +FROM lycheeverse/lychee:sha-2aa22f8@sha256:2e3786630482c41f9f2dd081e06d7da1c36d66996e8cf6573409b8bc418d48c4 AS lychee diff --git a/.github/scripts/link-check.sh b/.github/scripts/link-check.sh new file mode 100755 index 000000000..788e22830 --- /dev/null +++ b/.github/scripts/link-check.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +set -e + +export MSYS_NO_PATHCONV=1 # for Git Bash on Windows + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +LYCHEE_CONFIG="$SCRIPT_DIR/../../.lychee.toml" +DEPENDENCIES_DOCKERFILE="$SCRIPT_DIR/dependencies.dockerfile" + +# Extract lychee version from dependencies.dockerfile +LYCHEE_VERSION=$(grep "FROM lycheeverse/lychee:" "$DEPENDENCIES_DOCKERFILE" | sed 's/.*FROM lycheeverse\/lychee:\([^ ]*\).*/\1/') + +# Build the lychee command with optional GitHub token +CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --config $(basename "$LYCHEE_CONFIG")" + +# Add GitHub token if available +if [[ -n "$GITHUB_TOKEN" ]]; then + CMD="$CMD --github-token $GITHUB_TOKEN" +fi + +# Add the target directory +CMD="$CMD ." + +# Determine if we should allocate a TTY +DOCKER_FLAGS="--rm --init" +if [[ -t 0 ]]; then + DOCKER_FLAGS="$DOCKER_FLAGS -it" +else + DOCKER_FLAGS="$DOCKER_FLAGS -i" +fi + +# Run lychee with proper signal handling +# shellcheck disable=SC2086 +exec docker run $DOCKER_FLAGS -v "$(dirname "$LYCHEE_CONFIG")":/data -w /data $CMD diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4850fa48c..939716004 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -118,8 +118,12 @@ jobs: name: integration-test-results path: jmx-metrics/build/reports/tests/integrationTest - markdown-link-check: - uses: ./.github/workflows/reusable-markdown-link-check.yml + link-check: + # release branches are excluded to avoid unnecessary maintenance if external links break + # (and also because the README.md might need update on release branches before the release + # download has been published) + if: "!startsWith(github.ref_name, 'release/')" + uses: ./.github/workflows/reusable-link-check.yml markdown-lint-check: uses: ./.github/workflows/reusable-markdown-lint.yml @@ -142,7 +146,7 @@ jobs: # and so would not short-circuit if used in the second-last position name: publish-snapshots${{ (github.ref_name != 'main' || github.repository != 'open-telemetry/opentelemetry-java-contrib') && ' (skipped)' || '' }} needs: - # intentionally not blocking snapshot publishing on markdown-link-check or misspell-check + # intentionally not blocking snapshot publishing on link-check or misspell-check - build - integration-test runs-on: ubuntu-latest diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml new file mode 100644 index 000000000..044e85e1f --- /dev/null +++ b/.github/workflows/reusable-link-check.yml @@ -0,0 +1,18 @@ +name: Reusable - Link check + +on: + workflow_call: + +permissions: + contents: read + +jobs: + link-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + + - name: Link check + env: + GITHUB_TOKEN: ${{ github.token }} + run: ./.github/scripts/link-check.sh diff --git a/.github/workflows/reusable-markdown-link-check.yml b/.github/workflows/reusable-markdown-link-check.yml deleted file mode 100644 index 3120678ce..000000000 --- a/.github/workflows/reusable-markdown-link-check.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Reusable - Markdown link check - -on: - workflow_call: - -permissions: - contents: read - -jobs: - markdown-link-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - - uses: lycheeverse/lychee-action@82202e5e9c2f4ef1a55a3d02563e1cb6041e5332 # v2.4.1 - with: - # excluding links to pull requests and issues is done for performance - args: > - --include-fragments - --exclude "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$" - --max-retries 6 - --max-concurrency 1 - . diff --git a/.lychee.toml b/.lychee.toml new file mode 100644 index 000000000..d368a2404 --- /dev/null +++ b/.lychee.toml @@ -0,0 +1,16 @@ +timeout = 30 +retry_wait_time = 5 +max_retries = 6 +max_concurrency = 4 + +# Check link anchors +include_fragments = true + +# excluding links to pull requests and issues is done for performance +# sonatype snapshots are currrently unbrowseable +exclude = [ + "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$", + '^https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/contrib/$', +] + + From 26143b21d23994a83d0c767b2bb802afb80dafe3 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 2 Jul 2025 17:41:26 +0200 Subject: [PATCH 105/371] code stable semconv (#1989) --- inferred-spans/build.gradle.kts | 3 ++- .../inferredspans/internal/CallTree.java | 4 ++-- .../internal/semconv/Attributes.java | 2 -- .../internal/CallTreeSpanifyTest.java | 10 +++++----- .../inferredspans/internal/CallTreeTest.java | 4 ++-- .../internal/semconv/AttributesTest.java | 19 ------------------- span-stacktrace/build.gradle.kts | 3 ++- .../stacktrace/StackTraceSpanProcessor.java | 10 +++------- .../StackTraceSpanProcessorTest.java | 7 +++---- 9 files changed, 19 insertions(+), 43 deletions(-) delete mode 100644 inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/semconv/AttributesTest.java diff --git a/inferred-spans/build.gradle.kts b/inferred-spans/build.gradle.kts index d0921a940..98d5e33a3 100644 --- a/inferred-spans/build.gradle.kts +++ b/inferred-spans/build.gradle.kts @@ -13,6 +13,7 @@ dependencies { compileOnly("com.google.auto.service:auto-service-annotations") compileOnly("io.opentelemetry:opentelemetry-sdk") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly("io.opentelemetry.semconv:opentelemetry-semconv") implementation("com.lmax:disruptor") implementation("org.jctools:jctools-core") implementation("tools.profiler:async-profiler") @@ -21,7 +22,7 @@ dependencies { testAnnotationProcessor("com.google.auto.service:auto-service") testCompileOnly("com.google.auto.service:auto-service-annotations") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") testImplementation("io.opentelemetry:opentelemetry-sdk") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/CallTree.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/CallTree.java index 74468a3fd..a4a59a037 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/CallTree.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/CallTree.java @@ -5,7 +5,6 @@ package io.opentelemetry.contrib.inferredspans.internal; -import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.CODE_STACKTRACE; import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.LINK_IS_CHILD; import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.SPAN_IS_INFERRED; import static java.util.logging.Level.FINE; @@ -20,6 +19,7 @@ import io.opentelemetry.contrib.inferredspans.internal.pooling.ObjectPool; import io.opentelemetry.contrib.inferredspans.internal.pooling.Recyclable; import io.opentelemetry.contrib.inferredspans.internal.util.HexUtils; +import io.opentelemetry.semconv.CodeAttributes; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -515,7 +515,7 @@ protected Span asSpan( assert this.parent != null; tempBuilder.setLength(0); this.parent.fillStackTrace(tempBuilder); - spanBuilder.setAttribute(CODE_STACKTRACE, tempBuilder.toString()); + spanBuilder.setAttribute(CodeAttributes.CODE_STACKTRACE, tempBuilder.toString()); } Span span = spanBuilder.startSpan(); diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/semconv/Attributes.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/semconv/Attributes.java index dddb3a738..c10662231 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/semconv/Attributes.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/semconv/Attributes.java @@ -11,8 +11,6 @@ public class Attributes { private Attributes() {} - public static final AttributeKey CODE_STACKTRACE = - AttributeKey.stringKey("code.stacktrace"); public static final AttributeKey LINK_IS_CHILD = AttributeKey.booleanKey("is_child"); public static final AttributeKey SPAN_IS_INFERRED = AttributeKey.booleanKey("is_inferred"); diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeSpanifyTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeSpanifyTest.java index 8eddda06f..057fab819 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeSpanifyTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeSpanifyTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.contrib.inferredspans.internal; -import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.CODE_STACKTRACE; import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.SPAN_IS_INFERRED; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; @@ -21,6 +20,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; +import io.opentelemetry.semconv.CodeAttributes; import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -64,26 +64,26 @@ void testSpanification() throws Exception { SpanData a = setup.getSpans().get(1); assertThat(a).hasName("CallTreeTest#a"); assertThat(a.getEndEpochNanos() - a.getStartEpochNanos()).isEqualTo(30_000_000); - assertThat(a.getAttributes().get(CODE_STACKTRACE)).isBlank(); + assertThat(a.getAttributes().get(CodeAttributes.CODE_STACKTRACE)).isBlank(); assertThat(a).hasAttribute(SPAN_IS_INFERRED, true); SpanData b = setup.getSpans().get(2); assertThat(b).hasName("CallTreeTest#b"); assertThat(b.getEndEpochNanos() - b.getStartEpochNanos()).isEqualTo(20_000_000); - assertThat(b.getAttributes().get(CODE_STACKTRACE)).isBlank(); + assertThat(b.getAttributes().get(CodeAttributes.CODE_STACKTRACE)).isBlank(); assertThat(b).hasAttribute(SPAN_IS_INFERRED, true); SpanData d = setup.getSpans().get(3); assertThat(d).hasName("CallTreeTest#d"); assertThat(d.getEndEpochNanos() - d.getStartEpochNanos()).isEqualTo(10_000_000); - assertThat(d.getAttributes().get(CODE_STACKTRACE)) + assertThat(d.getAttributes().get(CodeAttributes.CODE_STACKTRACE)) .isEqualTo("at " + CallTreeTest.class.getName() + ".c(CallTreeTest.java)"); assertThat(d).hasAttribute(SPAN_IS_INFERRED, true); SpanData e = setup.getSpans().get(4); assertThat(e).hasName("CallTreeTest#e"); assertThat(e.getEndEpochNanos() - e.getStartEpochNanos()).isEqualTo(10_000_000); - assertThat(e.getAttributes().get(CODE_STACKTRACE)).isBlank(); + assertThat(e.getAttributes().get(CodeAttributes.CODE_STACKTRACE)).isBlank(); assertThat(e).hasAttribute(SPAN_IS_INFERRED, true); } } diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java index 8692158a6..b3d5df33a 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java @@ -5,7 +5,6 @@ package io.opentelemetry.contrib.inferredspans.internal; -import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.CODE_STACKTRACE; import static io.opentelemetry.contrib.inferredspans.internal.semconv.Attributes.LINK_IS_CHILD; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static java.util.stream.Collectors.toMap; @@ -20,6 +19,7 @@ import io.opentelemetry.contrib.inferredspans.internal.util.DisabledOnOpenJ9; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.semconv.CodeAttributes; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; @@ -924,7 +924,7 @@ private Map assertCallTree( .describedAs("Unexpected duration for span %s", span) .isEqualTo(durationMs * 1_000_000L); - String actualStacktrace = span.getAttributes().get(CODE_STACKTRACE); + String actualStacktrace = span.getAttributes().get(CodeAttributes.CODE_STACKTRACE); if (stackTrace == null || stackTrace.isEmpty()) { assertThat(actualStacktrace).isBlank(); } else { diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/semconv/AttributesTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/semconv/AttributesTest.java deleted file mode 100644 index cc39c4960..000000000 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/semconv/AttributesTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.inferredspans.internal.semconv; - -import static org.assertj.core.api.Assertions.assertThat; - -import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; -import org.junit.jupiter.api.Test; - -public class AttributesTest { - - @Test - public void checkCodeStacktraceUpToDate() { - assertThat(Attributes.CODE_STACKTRACE).isEqualTo(CodeIncubatingAttributes.CODE_STACKTRACE); - } -} diff --git a/span-stacktrace/build.gradle.kts b/span-stacktrace/build.gradle.kts index b8316af62..4033b0177 100644 --- a/span-stacktrace/build.gradle.kts +++ b/span-stacktrace/build.gradle.kts @@ -18,7 +18,8 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") - testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + compileOnly("io.opentelemetry.semconv:opentelemetry-semconv") + testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") testAnnotationProcessor("com.google.auto.service:auto-service") testCompileOnly("com.google.auto.service:auto-service-annotations") diff --git a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java index 62fb9ff34..3565957b1 100644 --- a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java @@ -5,21 +5,17 @@ package io.opentelemetry.contrib.stacktrace; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.ReadWriteSpan; import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.internal.ExtendedSpanProcessor; +import io.opentelemetry.semconv.CodeAttributes; import java.io.PrintWriter; import java.io.StringWriter; import java.util.function.Predicate; public class StackTraceSpanProcessor implements ExtendedSpanProcessor { - // inlined incubating attribute to prevent direct dependency on incubating semconv - private static final AttributeKey SPAN_STACKTRACE = - AttributeKey.stringKey("code.stacktrace"); - private final long minSpanDurationNanos; private final Predicate filterPredicate; @@ -56,14 +52,14 @@ public void onEnding(ReadWriteSpan span) { if (span.getLatencyNanos() < minSpanDurationNanos) { return; } - if (span.getAttribute(SPAN_STACKTRACE) != null) { + if (span.getAttribute(CodeAttributes.CODE_STACKTRACE) != null) { // Span already has a stacktrace, do not override return; } if (!filterPredicate.test(span)) { return; } - span.setAttribute(SPAN_STACKTRACE, generateSpanEndStacktrace()); + span.setAttribute(CodeAttributes.CODE_STACKTRACE, generateSpanEndStacktrace()); } @Override diff --git a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java index 3d6d4686d..e5be32019 100644 --- a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java +++ b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessorTest.java @@ -19,7 +19,7 @@ import io.opentelemetry.sdk.trace.ReadableSpan; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; -import io.opentelemetry.semconv.incubating.CodeIncubatingAttributes; +import io.opentelemetry.semconv.CodeAttributes; import java.time.Duration; import java.time.Instant; import java.util.HashMap; @@ -89,7 +89,7 @@ void spanWithExistingStackTrace() { YesPredicate.class, "1ms", Duration.ofMillis(1).toNanos(), - sb -> sb.setAttribute(CodeIncubatingAttributes.CODE_STACKTRACE, "hello"), + sb -> sb.setAttribute(CodeAttributes.CODE_STACKTRACE, "hello"), stacktrace -> assertThat(stacktrace).isEqualTo("hello")); } @@ -169,8 +169,7 @@ private static void checkSpan( List finishedSpans = spansExporter.getFinishedSpanItems(); assertThat(finishedSpans).hasSize(1); - String stackTrace = - finishedSpans.get(0).getAttributes().get(CodeIncubatingAttributes.CODE_STACKTRACE); + String stackTrace = finishedSpans.get(0).getAttributes().get(CodeAttributes.CODE_STACKTRACE); stackTraceCheck.accept(stackTrace); } From a6e06d0049035388df4c759632d90f5e0dfd1ada Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 14:01:22 -0700 Subject: [PATCH 106/371] fix(deps): update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.17.0-alpha (#1983) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- .../jmxscraper/target_systems/JvmIntegrationTest.java | 6 ------ .../target_systems/TargetSystemIntegrationTest.java | 6 ++++++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 8a78becf5..7de233296 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.16.0-alpha" +val otelInstrumentationVersion = "2.17.0-alpha" val semconvVersion = "1.34.0" javaPlatform { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java index 29b698eb5..e29021a93 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java @@ -32,12 +32,6 @@ protected JmxScraperContainer customizeScraperContainer( JmxScraperContainer scraper, GenericContainer target, Path tempDir) { return scraper .withTargetSystem("jvm") - // Since JVM metrics were be added to instrumentation, the default "auto" source - // means that the definitions in instrumentation will be used, and thus this test will fail - // due to metrics differences, adding an explicit "legacy" source is required to continue - // testing the JVM metrics defined in this project. - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/pull/13392 - .withTargetSystemSource("legacy") // also testing custom yaml .withCustomYaml("custom-metrics.yaml"); } diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java index 9c58c07fc..e74e93180 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java @@ -143,6 +143,12 @@ protected void startContainers(Path tmpDir) { // Create and initialize scraper container scraper = new JmxScraperContainer(otlpEndpoint, scraperBaseImage()) + // Since JVM metrics were be added to instrumentation, the default "auto" source + // means that the definitions in instrumentation will be used, and thus tests will fail + // due to metrics differences, adding an explicit "legacy" source is required to + // continue + // testing metrics defined in this project. + .withTargetSystemSource("legacy") .withLogConsumer(new Slf4jLogConsumer(jmxScraperLogger)) .withNetwork(network) .withRmiServiceUrl(TARGET_SYSTEM_NETWORK_ALIAS, JMX_PORT); From 81a36172bbefefbe3316b1ad187625dd55a043c5 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 3 Jul 2025 15:27:44 -0700 Subject: [PATCH 107/371] Update change log for upcoming release (#1990) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9351011ac..2d43c0e87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,11 +2,24 @@ ## Unreleased +### Disk buffering + +- Shared storage + ([#1912](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1912)) +- Implementing ExtendedLogRecordData + ([#1918](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1918)) +- Add missing EventName to disk-buffering LogRecordDataMapper + ([#1950](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1950)) + ### GCP authentication extension - Update the internal implementation such that the required headers are retrieved from the Google Auth Library instead of manually constructing and passing them. ([#1860](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1860)) +- Add metrics support to auth extension + ([#1891](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1891)) +- Update ConfigurableOptions to read from ConfigProperties + ([#1904](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1904)) ### Inferred spans From 358e039e2656d08e99d34ca77a80f17b40199fb3 Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 21:25:42 -0700 Subject: [PATCH 108/371] Update version to 1.48.0-SNAPSHOT (#1994) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- CHANGELOG.md | 11 ++++++++++- version.gradle.kts | 4 ++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d43c0e87..f428090b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,15 @@ # Changelog -## Unreleased +## Version 1.47.0 (2025-07-03) + +### Disk buffering + +- Shared storage + ([#1912](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1912)) +- Implementing ExtendedLogRecordData + ([#1918](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1918)) +- Add missing EventName to disk-buffering LogRecordDataMapper + ([#1950](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1950)) ### Disk buffering diff --git a/version.gradle.kts b/version.gradle.kts index 0ae7508cb..4d162323b 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.47.0-SNAPSHOT" -val alphaVersion = "1.47.0-alpha-SNAPSHOT" +val stableVersion = "1.48.0-SNAPSHOT" +val alphaVersion = "1.48.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From 01680d8df58ca443f84c6f66b7352e328c70c764 Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Thu, 3 Jul 2025 22:39:26 -0700 Subject: [PATCH 109/371] Merge change log updates from release/v1.47.x (#1999) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f428090b0..80ad781b0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,6 @@ # Changelog -## Version 1.47.0 (2025-07-03) - -### Disk buffering - -- Shared storage - ([#1912](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1912)) -- Implementing ExtendedLogRecordData - ([#1918](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1918)) -- Add missing EventName to disk-buffering LogRecordDataMapper - ([#1950](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1950)) +## Version 1.47.0 (2025-07-04) ### Disk buffering From a6ff9a030cc1a1cd45a6558b290d5749b48e4aef Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 6 Jul 2025 22:31:57 -0700 Subject: [PATCH 110/371] Fix sporadically failing maven extension test (#2001) --- .../io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java index 0174a6283..e2cc37a28 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java @@ -120,7 +120,7 @@ public void testOverwrittenExporterConfiguration_3() { } finally { System.clearProperty("otel.exporter.otlp.endpoint"); System.clearProperty("otel.exporter.otlp.traces.endpoint"); - System.clearProperty("otel.exporter.otlp.traces.protocol"); + System.clearProperty("otel.traces.exporter"); } } From 8a65c8cbcf8508965fd27f22142d272d3ad8ba8b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 10:08:36 +0300 Subject: [PATCH 111/371] fix(deps): update all patch versions (#1992) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- compressors/compressor-zstd/build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.properties | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index e33241437..7df7e3512 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -135,7 +135,7 @@ testing { dependencies { implementation(project(project.path)) - implementation(enforcedPlatform("org.junit:junit-bom:5.13.2")) + implementation(enforcedPlatform("org.junit:junit-bom:5.13.3")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) diff --git a/compressors/compressor-zstd/build.gradle.kts b/compressors/compressor-zstd/build.gradle.kts index 7d52fb224..46a72759b 100644 --- a/compressors/compressor-zstd/build.gradle.kts +++ b/compressors/compressor-zstd/build.gradle.kts @@ -9,7 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.compressor.zstd") dependencies { api("io.opentelemetry:opentelemetry-exporter-common") - implementation("com.github.luben:zstd-jni:1.5.7-3") + implementation("com.github.luben:zstd-jni:1.5.7-4") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3735f265b..78cb6e16a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip +distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME From 81ef37bb5547f122d00a2fef517d5c9142b1fc2e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 7 Jul 2025 07:39:42 -0700 Subject: [PATCH 112/371] Add unreleased section (#2004) --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80ad781b0..deab78620 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Changelog +## Unreleased + ## Version 1.47.0 (2025-07-04) ### Disk buffering From b2c64b376599e1dbf0271e661a304f4b3ef726a7 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 7 Jul 2025 16:56:41 +0200 Subject: [PATCH 113/371] jmx scraper add link to instrumentation yaml for tomcat (#2003) --- jmx-scraper/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 0cfd03306..72602705f 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -70,7 +70,7 @@ Supported values for `otel.jmx.target.system` and support for `otel.jmx.target.s | `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | | `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | | `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | -| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | | +| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | [`tomcat.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml) | | `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | | The source of metrics definitions is controlled by `otel.jmx.target.source`: From 8add72d0569b958e5211909890d244551cbd2089 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 7 Jul 2025 11:21:23 -0700 Subject: [PATCH 114/371] fix(deps): update dependency com.squareup.okhttp3:okhttp to v5 (#2000) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7de233296..e491e0cc3 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -43,7 +43,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.squareup.okhttp3:okhttp:4.12.0") + api("com.squareup.okhttp3:okhttp:5.0.0") api("com.uber.nullaway:nullaway:0.12.7") api("org.assertj:assertj-core:3.27.3") api("org.awaitility:awaitility:4.3.0") From 8c6089aa593247a67faeb3d665cfe360024adb5c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 7 Jul 2025 14:40:32 -0700 Subject: [PATCH 115/371] Stop lychee from causing so many PR failures (#2002) --- .github/scripts/.lychee-relative.toml | 7 ++++ .lychee.toml => .github/scripts/.lychee.toml | 0 .github/scripts/link-check.sh | 39 +++++++++++++++++--- .github/workflows/build.yml | 31 ++++++++++++++++ .github/workflows/reusable-link-check.yml | 29 ++++++++++++++- 5 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 .github/scripts/.lychee-relative.toml rename .lychee.toml => .github/scripts/.lychee.toml (100%) diff --git a/.github/scripts/.lychee-relative.toml b/.github/scripts/.lychee-relative.toml new file mode 100644 index 000000000..03124f675 --- /dev/null +++ b/.github/scripts/.lychee-relative.toml @@ -0,0 +1,7 @@ +# Check link anchors +include_fragments = true + +# For relative links only, we exclude all external URLs +exclude = [ + "^https?://.*", +] diff --git a/.lychee.toml b/.github/scripts/.lychee.toml similarity index 100% rename from .lychee.toml rename to .github/scripts/.lychee.toml diff --git a/.github/scripts/link-check.sh b/.github/scripts/link-check.sh index 788e22830..f6d3ec7e7 100755 --- a/.github/scripts/link-check.sh +++ b/.github/scripts/link-check.sh @@ -5,22 +5,51 @@ set -e export MSYS_NO_PATHCONV=1 # for Git Bash on Windows SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -LYCHEE_CONFIG="$SCRIPT_DIR/../../.lychee.toml" +ROOT_DIR="$SCRIPT_DIR/../.." DEPENDENCIES_DOCKERFILE="$SCRIPT_DIR/dependencies.dockerfile" +# Parse command line arguments +RELATIVE_ONLY=false +MODIFIED_FILES="" + +while [[ $# -gt 0 ]]; do + case $1 in + --relative-only) + RELATIVE_ONLY=true + shift + ;; + *) + # Treat any other arguments as file paths + MODIFIED_FILES="$MODIFIED_FILES $1" + shift + ;; + esac +done + # Extract lychee version from dependencies.dockerfile LYCHEE_VERSION=$(grep "FROM lycheeverse/lychee:" "$DEPENDENCIES_DOCKERFILE" | sed 's/.*FROM lycheeverse\/lychee:\([^ ]*\).*/\1/') +# Determine target files/directories and config file +TARGET="." +LYCHEE_CONFIG=".github/scripts/.lychee.toml" + +if [[ "$RELATIVE_ONLY" == "true" ]]; then + LYCHEE_CONFIG=".github/scripts/.lychee-relative.toml" +fi + +if [[ -n "$MODIFIED_FILES" ]]; then + TARGET="$MODIFIED_FILES" +fi + # Build the lychee command with optional GitHub token -CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --config $(basename "$LYCHEE_CONFIG")" +CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --config $LYCHEE_CONFIG" # Add GitHub token if available if [[ -n "$GITHUB_TOKEN" ]]; then CMD="$CMD --github-token $GITHUB_TOKEN" fi -# Add the target directory -CMD="$CMD ." +CMD="$CMD $TARGET" # Determine if we should allocate a TTY DOCKER_FLAGS="--rm --init" @@ -32,4 +61,4 @@ fi # Run lychee with proper signal handling # shellcheck disable=SC2086 -exec docker run $DOCKER_FLAGS -v "$(dirname "$LYCHEE_CONFIG")":/data -w /data $CMD +exec docker run $DOCKER_FLAGS -v "$ROOT_DIR":/data -w /data $CMD diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 939716004..614ac9c10 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -8,6 +8,9 @@ on: pull_request: merge_group: workflow_dispatch: + schedule: + # Run daily at 7:30 AM UTC + - cron: '30 7 * * *' permissions: contents: read @@ -199,3 +202,31 @@ jobs: ) ) run: exit 1 # fail + + workflow-notification: + permissions: + contents: read + issues: write + if: github.event_name == 'schedule' && always() + needs: + - build + - test + - integration-test + - link-check + - markdown-lint-check + - misspell-check + - shell-script-check + - publish-snapshots + uses: ./.github/workflows/reusable-workflow-notification.yml + with: + success: >- + ${{ + needs.build.result == 'success' && + needs.test.result == 'success' && + needs.integration-test.result == 'success' && + needs.link-check.result == 'success' && + needs.markdown-lint-check.result == 'success' && + needs.misspell-check.result == 'success' && + needs.shell-script-check.result == 'success' && + needs.publish-snapshots.result == 'success' + }} diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 044e85e1f..4392808d1 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -11,8 +11,35 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + fetch-depth: 0 # needed for merge-base below - - name: Link check + - name: Link check - relative links (all files) + if: github.event_name == 'pull_request' + env: + GITHUB_TOKEN: ${{ github.token }} + run: ./.github/scripts/link-check.sh --relative-only + + - name: Get modified files + if: github.event_name == 'pull_request' + id: modified-files + run: | + merge_base=$(git merge-base origin/${{ github.base_ref }} HEAD) + # Using lychee's default extension filter here to match when it runs against all files + modified_files=$(git diff --name-only $merge_base...${{ github.event.pull_request.head.sha }} \ + | grep -E '\.(md|mkd|mdx|mdown|mdwn|mkdn|mkdown|markdown|html|htm|txt)$' \ + | tr '\n' ' ' || true) + echo "files=$modified_files" >> $GITHUB_OUTPUT + echo "Modified files: $modified_files" + + - name: Link check - all links (modified files only) + if: github.event_name == 'pull_request' && steps.modified-files.outputs.files != '' + env: + GITHUB_TOKEN: ${{ github.token }} + run: ./.github/scripts/link-check.sh ${{ steps.modified-files.outputs.files }} + + - name: Link check - all links (all files) + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' env: GITHUB_TOKEN: ${{ github.token }} run: ./.github/scripts/link-check.sh From 65179dc9dbf5b781e517f29d59655233cd87000f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 7 Jul 2025 22:36:12 -0700 Subject: [PATCH 116/371] Also open issue on workflow dispatch failure (#2005) --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 614ac9c10..50a3ad0f0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -207,7 +207,7 @@ jobs: permissions: contents: read issues: write - if: github.event_name == 'schedule' && always() + if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && always() needs: - build - test From 97382709ba72f20958ad21ea8b31d697a0bf1b56 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 8 Jul 2025 12:06:33 -0700 Subject: [PATCH 117/371] link check small refactoring (#2006) --- .github/workflows/build.yml | 5 ++++- .github/workflows/reusable-link-check.yml | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 50a3ad0f0..260b56660 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -122,10 +122,13 @@ jobs: path: jmx-metrics/build/reports/tests/integrationTest link-check: + # merge group and push events are excluded to avoid unnecessary CI failures + # (these failures will instead be captured by the daily scheduled run) + # # release branches are excluded to avoid unnecessary maintenance if external links break # (and also because the README.md might need update on release branches before the release # download has been published) - if: "!startsWith(github.ref_name, 'release/')" + if: github.event_name != 'merge_group' && github.event_name != 'push' && !startsWith(github.ref_name, 'release/') uses: ./.github/workflows/reusable-link-check.yml markdown-lint-check: diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 4392808d1..6e45185c7 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -39,7 +39,7 @@ jobs: run: ./.github/scripts/link-check.sh ${{ steps.modified-files.outputs.files }} - name: Link check - all links (all files) - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.event_name != 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} run: ./.github/scripts/link-check.sh From c3fc90f07604a901cfb5a4579eb8304fc5f2946a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 8 Jul 2025 21:00:49 -0700 Subject: [PATCH 118/371] chore(deps): update open-telemetry/assign-reviewers-action digest to fcd27c5 (#2008) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-reviewers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml index cd361767f..a21d65ed4 100644 --- a/.github/workflows/assign-reviewers.yml +++ b/.github/workflows/assign-reviewers.yml @@ -18,6 +18,6 @@ jobs: pull-requests: write # for assigning reviewers runs-on: ubuntu-latest steps: - - uses: open-telemetry/assign-reviewers-action@cb42e3ee14a59c01abccd401f126a0f4c3991cb3 # main + - uses: open-telemetry/assign-reviewers-action@fcd27c5381c10288b23d423ab85473710a33389e # main with: config-file: .github.amrom.workers.devponent_owners.yml From 5aaf88fa0a3ac1bbc7a683552cb4a9e5e44869ad Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 9 Jul 2025 08:46:27 +0300 Subject: [PATCH 119/371] chore(deps): update plugin com.squareup.wire to v5.3.4 (#2009) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- disk-buffering/build.gradle.kts | 2 +- opamp-client/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index b3a842b1d..f4b67f25f 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.github.johnrengelman.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.3" + id("com.squareup.wire") version "5.3.4" } description = "Exporter implementations that store signals on disk" diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index f3ad4cfda..6e557c3da 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import java.net.URL plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.3" + id("com.squareup.wire") version "5.3.4" } description = "Client implementation of the OpAMP spec." From 45ea93180ca1f54d5f645b0f72966066bc6a06f5 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 9 Jul 2025 14:58:24 -0700 Subject: [PATCH 120/371] Fix OSSF scorecard branch protection check (#2015) --- .github/workflows/ossf-scorecard.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index cfdb7ca62..c339c65ba 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,8 +23,18 @@ jobs: with: persist-credentials: false + - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + id: create-token + with: + # analyzing classic branch protections requires a token with admin read permissions + # see https://github.com/ossf/scorecard-action/blob/main/docs/authentication/fine-grained-auth-token.md + # and https://github.com/open-telemetry/community/issues/2769 + app-id: ${{ vars.OSSF_SCORECARD_APP_ID }} + private-key: ${{ secrets.OSSF_SCORECARD_PRIVATE_KEY }} + - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 with: + repo_token: ${{ steps.create-token.outputs.token }} results_file: results.sarif results_format: sarif publish_results: true From 83d5cd843dc1f4c61ef80555a82b5acea7ce3247 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 10 Jul 2025 08:55:16 +0300 Subject: [PATCH 121/371] fix(deps): update all patch versions (#2017) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- disk-buffering/build.gradle.kts | 2 +- opamp-client/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index e491e0cc3..1c2ab8ce4 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.17.0-alpha" +val otelInstrumentationVersion = "2.17.1-alpha" val semconvVersion = "1.34.0" javaPlatform { diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index f4b67f25f..8250c1bdf 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.github.johnrengelman.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.4" + id("com.squareup.wire") version "5.3.5" } description = "Exporter implementations that store signals on disk" diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 6e557c3da..a80f93932 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import java.net.URL plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.4" + id("com.squareup.wire") version "5.3.5" } description = "Client implementation of the OpAMP spec." From 6f3a18a1098fbd8acb2d2f7a562f08bedcd935d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Thu, 10 Jul 2025 15:21:23 +0200 Subject: [PATCH 122/371] OpAMP WebSocket service (#1969) Co-authored-by: Lauri Tulmin Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 1 + opamp-client/build.gradle.kts | 1 + .../websocket/OkHttpWebSocket.java | 115 ++++++ .../connectivity/websocket/WebSocket.java | 72 ++++ .../request/service/DaemonThreadFactory.java | 25 ++ .../request/service/HttpRequestService.java | 17 - .../service/WebSocketRequestService.java | 271 ++++++++++++++ .../websocket/OkHttpWebSocketTest.java | 152 ++++++++ .../service/HttpRequestServiceTest.java | 159 ++------- .../service/PeriodicDelayWithSuggestion.java | 35 ++ .../request/service/TestScheduler.java | 120 +++++++ .../service/WebSocketRequestServiceTest.java | 336 ++++++++++++++++++ 12 files changed, 1151 insertions(+), 153 deletions(-) create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/WebSocket.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/DaemonThreadFactory.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocketTest.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/PeriodicDelayWithSuggestion.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/TestScheduler.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 1c2ab8ce4..0df38930b 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.1")) + api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.31.1")) constraints { api("io.opentelemetry.semconv:opentelemetry-semconv:${semconvVersion}") diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index a80f93932..e0a7f8431 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") testImplementation("org.mockito:mockito-inline") + testImplementation("com.google.protobuf:protobuf-java-util") } val opampProtos = tasks.register("opampProtoDownload", download) diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java new file mode 100644 index 000000000..ec453bf6f --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java @@ -0,0 +1,115 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.websocket; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import okhttp3.OkHttpClient; +import okhttp3.Response; +import okhttp3.WebSocketListener; +import okio.ByteString; + +public class OkHttpWebSocket implements WebSocket { + private final String url; + private final OkHttpClient client; + private final AtomicReference status = new AtomicReference<>(Status.NOT_RUNNING); + private final AtomicReference webSocket = new AtomicReference<>(); + + public static OkHttpWebSocket create(String url) { + return create(url, new OkHttpClient()); + } + + public static OkHttpWebSocket create(String url, OkHttpClient client) { + return new OkHttpWebSocket(url, client); + } + + private OkHttpWebSocket(String url, OkHttpClient client) { + this.url = url; + this.client = client; + } + + @Override + public void open(Listener listener) { + if (status.compareAndSet(Status.NOT_RUNNING, Status.STARTING)) { + okhttp3.Request request = new okhttp3.Request.Builder().url(url).build(); + webSocket.set(client.newWebSocket(request, new ListenerAdapter(listener))); + } + } + + @Override + public boolean send(byte[] request) { + if (status.get() != Status.RUNNING) { + return false; + } + return getWebSocket().send(ByteString.of(request)); + } + + @Override + public void close(int code, @Nullable String reason) { + if (status.compareAndSet(Status.RUNNING, Status.CLOSING)) { + try { + if (!getWebSocket().close(code, reason)) { + status.set(Status.NOT_RUNNING); + } + } catch (IllegalArgumentException e) { + status.set(Status.RUNNING); + // Re-throwing as this error happens due to a caller error. + throw e; + } + } + } + + private okhttp3.WebSocket getWebSocket() { + return Objects.requireNonNull(webSocket.get()); + } + + private class ListenerAdapter extends WebSocketListener { + private final Listener delegate; + + private ListenerAdapter(Listener delegate) { + this.delegate = delegate; + } + + @Override + public void onOpen(@Nonnull okhttp3.WebSocket webSocket, @Nonnull Response response) { + status.set(Status.RUNNING); + delegate.onOpen(); + } + + @Override + public void onClosing(@Nonnull okhttp3.WebSocket webSocket, int code, @Nonnull String reason) { + status.set(Status.CLOSING); + delegate.onClosing(); + } + + @Override + public void onClosed(@Nonnull okhttp3.WebSocket webSocket, int code, @Nonnull String reason) { + status.set(Status.NOT_RUNNING); + delegate.onClosed(); + } + + @Override + public void onFailure( + @Nonnull okhttp3.WebSocket webSocket, @Nonnull Throwable t, @Nullable Response response) { + status.set(Status.NOT_RUNNING); + delegate.onFailure(t); + } + + @Override + public void onMessage(@Nonnull okhttp3.WebSocket webSocket, @Nonnull ByteString bytes) { + delegate.onMessage(bytes.toByteArray()); + } + } + + enum Status { + NOT_RUNNING, + STARTING, + CLOSING, + RUNNING + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/WebSocket.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/WebSocket.java new file mode 100644 index 000000000..56978bbef --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/WebSocket.java @@ -0,0 +1,72 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.websocket; + +import javax.annotation.Nullable; + +public interface WebSocket { + /** + * Starts the websocket connection if it's not yet started or if it has been closed. + * + * @param listener Will receive events from the websocket connection. + */ + void open(Listener listener); + + /** + * Stops the websocket connection if running. Nothing will happen if it's already stopped. + * + * @param code Status code as defined by Section 7.4 of RFC 6455 + * @param reason Reason for shutting down, as explained in Section 5.5.1 of RFC + * 6455 + */ + void close(int code, @Nullable String reason); + + /** + * Sends a message via the websocket connection. + * + * @param request The message payload. + * @return {@code false} If the message can't be dispatched for any reason, whether the websocket + * isn't running, or the connection isn't established, or it's terminated. {@code true} if the + * message can get sent. Returning {@code true} doesn't guarantee that the message will arrive + * at the remote peer. + */ + boolean send(byte[] request); + + interface Listener { + + /** + * Called when the websocket connection is successfully established with the remote peer. The + * client may start sending messages after this method is called. + */ + void onOpen(); + + /** + * Called when the closing handshake has started. No further messages will be sent after this + * method call. + */ + void onClosing(); + + /** Called when the connection is terminated and no further messages can be transmitted. */ + void onClosed(); + + /** + * Called when receiving a message from the remote peer. + * + * @param data The payload sent by the remote peer. + */ + void onMessage(byte[] data); + + /** + * Called when the connection is closed or cannot be established due to an error. No messages + * can be transmitted after this method is called. + * + * @param t The error. + */ + void onFailure(Throwable t); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/DaemonThreadFactory.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/DaemonThreadFactory.java new file mode 100644 index 000000000..77f84f72a --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/DaemonThreadFactory.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadFactory; +import javax.annotation.Nonnull; + +final class DaemonThreadFactory implements ThreadFactory { + private final ThreadFactory delegate = Executors.defaultThreadFactory(); + + @Override + public Thread newThread(@Nonnull Runnable r) { + Thread t = delegate.newThread(r); + try { + t.setDaemon(true); + } catch (SecurityException e) { + // Well, we tried. + } + return t; + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java index 32d5a60f1..1b196218b 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java @@ -22,13 +22,11 @@ import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Supplier; -import javax.annotation.Nonnull; import javax.annotation.Nullable; import opamp.proto.AgentToServer; import opamp.proto.ServerErrorResponse; @@ -255,19 +253,4 @@ Duration getNextDelay() { return currentDelay.getNextDelay(); } } - - private static class DaemonThreadFactory implements ThreadFactory { - private final ThreadFactory delegate = Executors.defaultThreadFactory(); - - @Override - public Thread newThread(@Nonnull Runnable r) { - Thread t = delegate.newThread(r); - try { - t.setDaemon(true); - } catch (SecurityException e) { - // Well, we tried. - } - return t; - } - } } diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java new file mode 100644 index 000000000..a55a1be70 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java @@ -0,0 +1,271 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import com.squareup.wire.ProtoAdapter; +import io.opentelemetry.opamp.client.internal.connectivity.websocket.WebSocket; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; +import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseError; +import io.opentelemetry.opamp.client.internal.response.Response; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; +import opamp.proto.ServerErrorResponse; +import opamp.proto.ServerErrorResponseType; +import opamp.proto.ServerToAgent; + +public final class WebSocketRequestService implements RequestService, WebSocket.Listener { + private static final PeriodicDelay DEFAULT_DELAY_BETWEEN_RETRIES = + PeriodicDelay.ofFixedDuration(Duration.ofSeconds(30)); + + private final WebSocket webSocket; + private final AtomicBoolean isRunning = new AtomicBoolean(false); + private final AtomicBoolean hasStopped = new AtomicBoolean(false); + private final ConnectionStatus connectionStatus; + private final ScheduledExecutorService executorService; + + /** Defined here. */ + private static final int WEBSOCKET_NORMAL_CLOSURE_CODE = 1000; + + @GuardedBy("hasPendingRequestLock") + private boolean hasPendingRequest = false; + + private final Object hasPendingRequestLock = new Object(); + @Nullable private Callback callback; + @Nullable private Supplier requestSupplier; + + /** + * Creates an {@link WebSocketRequestService}. + * + * @param webSocket The WebSocket implementation. + */ + public static WebSocketRequestService create(WebSocket webSocket) { + return create(webSocket, DEFAULT_DELAY_BETWEEN_RETRIES); + } + + /** + * Creates an {@link WebSocketRequestService}. + * + * @param webSocket The WebSocket implementation. + * @param periodicRetryDelay The time to wait between retries. + */ + public static WebSocketRequestService create( + WebSocket webSocket, PeriodicDelay periodicRetryDelay) { + return new WebSocketRequestService( + webSocket, + periodicRetryDelay, + Executors.newSingleThreadScheduledExecutor(new DaemonThreadFactory())); + } + + WebSocketRequestService( + WebSocket webSocket, + PeriodicDelay periodicRetryDelay, + ScheduledExecutorService executorService) { + this.webSocket = webSocket; + this.executorService = executorService; + this.connectionStatus = new ConnectionStatus(periodicRetryDelay); + } + + @Override + public void start(Callback callback, Supplier requestSupplier) { + if (hasStopped.get()) { + throw new IllegalStateException("This service is already stopped"); + } + if (isRunning.compareAndSet(false, true)) { + this.callback = callback; + this.requestSupplier = requestSupplier; + startConnection(); + } else { + throw new IllegalStateException("The service has already started"); + } + } + + private void startConnection() { + webSocket.open(this); + } + + @Override + public void sendRequest() { + if (!isRunning.get()) { + throw new IllegalStateException("The service is not running"); + } + if (hasStopped.get()) { + throw new IllegalStateException("This service is already stopped"); + } + + doSendRequest(); + } + + private void doSendRequest() { + try { + synchronized (hasPendingRequestLock) { + if (!trySendRequest()) { + hasPendingRequest = true; + } + } + } catch (IOException e) { + getCallback().onRequestFailed(e); + } + } + + private boolean trySendRequest() throws IOException { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + ProtoAdapter.UINT64.encode(outputStream, 0L); + byte[] payload = getRequest().getAgentToServer().encode(); + outputStream.write(payload); + return webSocket.send(outputStream.toByteArray()); + } + } + + @Nonnull + private Request getRequest() { + return Objects.requireNonNull(requestSupplier).get(); + } + + @Override + public void stop() { + if (hasStopped.compareAndSet(false, true)) { + /* + Sending last message as explained in the spec: + https://opentelemetry.io/docs/specs/opamp/#websocket-transport-opamp-client-initiated. + The client implementation must ensure that the "agent_disconnect" field will be provided in the + next supplied request body. + */ + doSendRequest(); + webSocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, null); + executorService.shutdown(); + } + } + + @Override + public void onOpen() { + connectionStatus.success(); + getCallback().onConnectionSuccess(); + synchronized (hasPendingRequestLock) { + if (hasPendingRequest) { + hasPendingRequest = false; + sendRequest(); + } + } + } + + @Override + public void onMessage(byte[] data) { + try { + ServerToAgent serverToAgent = readServerToAgent(data); + + if (serverToAgent.error_response != null) { + handleServerError(serverToAgent.error_response); + getCallback() + .onRequestFailed( + new OpampServerResponseError(serverToAgent.error_response.error_message)); + return; + } + + getCallback().onRequestSuccess(Response.create(serverToAgent)); + } catch (IOException e) { + getCallback().onRequestFailed(e); + } + } + + private static ServerToAgent readServerToAgent(byte[] data) throws IOException { + int headerSize = ProtoAdapter.UINT64.encodedSize(ProtoAdapter.UINT64.decode(data)); + int payloadSize = data.length - headerSize; + byte[] payload = new byte[payloadSize]; + System.arraycopy(data, headerSize, payload, 0, payloadSize); + return ServerToAgent.ADAPTER.decode(payload); + } + + private void handleServerError(ServerErrorResponse errorResponse) { + if (serverIsUnavailable(errorResponse)) { + Duration retryAfter = null; + + if (errorResponse.retry_info != null) { + retryAfter = Duration.ofNanos(errorResponse.retry_info.retry_after_nanoseconds); + } + + webSocket.close(WEBSOCKET_NORMAL_CLOSURE_CODE, null); + connectionStatus.retryAfter(retryAfter); + } + } + + private static boolean serverIsUnavailable(ServerErrorResponse errorResponse) { + return errorResponse.type.equals(ServerErrorResponseType.ServerErrorResponseType_Unavailable); + } + + @Override + public void onClosing() { + // Noop + } + + @Override + public void onClosed() { + // If this service isn't stopped, we should retry connecting. + connectionStatus.retryAfter(null); + } + + @Override + public void onFailure(Throwable t) { + getCallback().onConnectionFailed(t); + connectionStatus.retryAfter(null); + } + + @Nonnull + private Callback getCallback() { + return Objects.requireNonNull(callback); + } + + private class ConnectionStatus { + private final PeriodicDelay periodicRetryDelay; + private final AtomicBoolean retryingConnection = new AtomicBoolean(false); + private final AtomicBoolean nextRetryScheduled = new AtomicBoolean(false); + + ConnectionStatus(PeriodicDelay periodicRetryDelay) { + this.periodicRetryDelay = periodicRetryDelay; + } + + void success() { + retryingConnection.set(false); + } + + @SuppressWarnings("FutureReturnValueIgnored") + void retryAfter(@Nullable Duration retryAfter) { + if (hasStopped.get()) { + return; + } + + if (retryingConnection.compareAndSet(false, true)) { + periodicRetryDelay.reset(); + if (retryAfter != null && periodicRetryDelay instanceof AcceptsDelaySuggestion) { + ((AcceptsDelaySuggestion) periodicRetryDelay).suggestDelay(retryAfter); + } + } + + if (nextRetryScheduled.compareAndSet(false, true)) { + executorService.schedule( + this::retryConnection, + periodicRetryDelay.getNextDelay().toNanos(), + TimeUnit.NANOSECONDS); + } + } + + private void retryConnection() { + nextRetryScheduled.set(false); + startConnection(); + } + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocketTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocketTest.java new file mode 100644 index 000000000..ab84b3881 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocketTest.java @@ -0,0 +1,152 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.connectivity.websocket; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.WebSocketListener; +import okio.ByteString; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class OkHttpWebSocketTest { + @Mock private OkHttpClient client; + @Mock private okhttp3.WebSocket okHttpWebSocket; + @Mock private WebSocket.Listener listener; + @Captor private ArgumentCaptor requestCaptor; + @Captor private ArgumentCaptor listenerCaptor; + private static final String URL = "ws://some.server"; + private OkHttpWebSocket webSocket; + + @BeforeEach + void setUp() { + webSocket = OkHttpWebSocket.create(URL, client); + when(client.newWebSocket(any(), any())).thenReturn(okHttpWebSocket); + } + + @Test + void validateOpen() { + // Assert websocket created + openAndCaptureArguments(); + assertThat(requestCaptor.getValue().url().host()).isEqualTo("some.server"); + + // Assert further calls to open won't do anything + webSocket.open(listener); + verifyNoMoreInteractions(client); + + // When connectivity succeeds, open calls won't do anything. + callOnOpen(); + webSocket.open(listener); + verifyNoMoreInteractions(client); + + // When connectivity fails, allow future open calls + clearInvocations(client); + callOnFailure(); + openAndCaptureArguments(); + assertThat(requestCaptor.getValue().url().host()).isEqualTo("some.server"); + } + + @Test + void validateSend() { + byte[] payload = new byte[1]; + + // Before opening + assertThat(webSocket.send(payload)).isFalse(); + + // After opening successfully + when(okHttpWebSocket.send(any(ByteString.class))).thenReturn(true); + openAndCaptureArguments(); + callOnOpen(); + assertThat(webSocket.send(payload)).isTrue(); + verify(okHttpWebSocket).send(ByteString.of(payload)); + + // After failing + callOnFailure(); + assertThat(webSocket.send(payload)).isFalse(); + verifyNoMoreInteractions(okHttpWebSocket); + } + + @Test + void validateClose() { + openAndCaptureArguments(); + + callOnOpen(); + webSocket.close(123, "something"); + verify(okHttpWebSocket).close(123, "something"); + + // Validate calling it again + webSocket.close(1, null); + verifyNoMoreInteractions(okHttpWebSocket); + + // Once closed, it should be possible to reopen it. + clearInvocations(client); + callOnClosed(); + openAndCaptureArguments(); + assertThat(requestCaptor.getValue().url().host()).isEqualTo("some.server"); + } + + @Test + void validateOnClosing() { + openAndCaptureArguments(); + + callOnOpen(); + callOnClosing(); + + // Validate calling after onClosing + webSocket.close(1, null); + verifyNoInteractions(okHttpWebSocket); + } + + @Test + void validateOnMessage() { + byte[] payload = new byte[1]; + openAndCaptureArguments(); + + listenerCaptor.getValue().onMessage(mock(), ByteString.of(payload)); + verify(listener).onMessage(payload); + } + + private void callOnOpen() { + listenerCaptor.getValue().onOpen(mock(), mock()); + verify(listener).onOpen(); + } + + private void callOnClosed() { + listenerCaptor.getValue().onClosed(mock(), 0, ""); + verify(listener).onClosed(); + } + + private void callOnClosing() { + listenerCaptor.getValue().onClosing(mock(), 0, ""); + verify(listener).onClosing(); + } + + private void callOnFailure() { + Throwable t = mock(); + listenerCaptor.getValue().onFailure(mock(), t, mock()); + verify(listener).onFailure(t); + } + + private void openAndCaptureArguments() { + webSocket.open(listener); + verify(client).newWebSocket(requestCaptor.capture(), listenerCaptor.capture()); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java index 63439e8c6..9996c4783 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java @@ -8,10 +8,8 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -21,7 +19,6 @@ import io.opentelemetry.opamp.client.internal.connectivity.http.HttpSender; import io.opentelemetry.opamp.client.internal.connectivity.http.RetryAfterParser; import io.opentelemetry.opamp.client.internal.request.Request; -import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; import io.opentelemetry.opamp.client.internal.response.Response; import java.io.ByteArrayInputStream; @@ -33,10 +30,7 @@ import java.util.NoSuchElementException; import java.util.Queue; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Delayed; import java.util.concurrent.ExecutionException; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import opamp.proto.AgentToServer; @@ -44,7 +38,6 @@ import opamp.proto.ServerErrorResponse; import opamp.proto.ServerErrorResponseType; import opamp.proto.ServerToAgent; -import org.jetbrains.annotations.NotNull; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -61,8 +54,7 @@ class HttpRequestServiceTest { private static final Duration RETRY_DELAY = Duration.ofSeconds(5); @Mock private RequestService.Callback callback; - private final List scheduledTasks = new ArrayList<>(); - private ScheduledExecutorService executorService; + private TestScheduler scheduler; private TestHttpSender requestSender; private PeriodicDelay periodicRequestDelay; private PeriodicDelayWithSuggestion periodicRetryDelay; @@ -74,42 +66,40 @@ void setUp() { requestSender = new TestHttpSender(); periodicRequestDelay = createPeriodicDelay(REGULAR_DELAY); periodicRetryDelay = createPeriodicDelayWithSuggestionSupport(RETRY_DELAY); - executorService = createTestScheduleExecutorService(); + scheduler = new TestScheduler(); httpRequestService = new HttpRequestService( requestSender, - executorService, + scheduler.getMockService(), periodicRequestDelay, periodicRetryDelay, RetryAfterParser.getInstance()); - httpRequestService.start(callback, this::createRequestSupplier); + httpRequestService.start(callback, this::createRequest); } @AfterEach void tearDown() { httpRequestService.stop(); - scheduledTasks.clear(); - verify(executorService).shutdown(); + verify(scheduler.getMockService()).shutdown(); } @Test void verifyStart_scheduledFirstTask() { - assertThat(scheduledTasks).hasSize(1); - ScheduledTask firstTask = scheduledTasks.get(0); - assertThat(firstTask.delay).isEqualTo(REGULAR_DELAY); + TestScheduler.Task firstTask = assertAndGetSingleCurrentTask(); + assertThat(firstTask.getDelay()).isEqualTo(REGULAR_DELAY); // Verify initial task creates next one - scheduledTasks.clear(); + scheduler.clearTasks(); requestSender.enqueueResponse(createSuccessfulResponse(new ServerToAgent.Builder().build())); firstTask.run(); - assertThat(scheduledTasks).hasSize(1); + assertThat(scheduler.getScheduledTasks()).hasSize(1); // Check on-demand requests don't create subsequent tasks requestSender.enqueueResponse(createSuccessfulResponse(new ServerToAgent.Builder().build())); httpRequestService.sendRequest(); - assertThat(scheduledTasks).hasSize(1); + assertThat(scheduler.getScheduledTasks()).hasSize(1); } @Test @@ -128,14 +118,14 @@ void verifySendingRequest_happyPath() { @Test void verifyWhenSendingOnDemandRequest_andDelayChanges() { // Initial state - assertThat(assertAndGetSingleCurrentTask().delay).isEqualTo(REGULAR_DELAY); + assertThat(assertAndGetSingleCurrentTask().getDelay()).isEqualTo(REGULAR_DELAY); // Trigger delay strategy change requestSender.enqueueResponse(createFailedResponse(503)); httpRequestService.sendRequest(); // Expected state - assertThat(assertAndGetSingleCurrentTask().delay).isEqualTo(RETRY_DELAY); + assertThat(assertAndGetSingleCurrentTask().getDelay()).isEqualTo(RETRY_DELAY); } @Test @@ -252,30 +242,30 @@ void verifySendingRequest_duringRegularMode() { private void verifyRetryDelayOnError( HttpSender.Response errorResponse, Duration expectedRetryDelay) { requestSender.enqueueResponse(errorResponse); - ScheduledTask previousTask = assertAndGetSingleCurrentTask(); + TestScheduler.Task previousTask = assertAndGetSingleCurrentTask(); previousTask.run(); verifySingleRequestSent(); verify(periodicRetryDelay).reset(); verify(callback).onRequestFailed(any()); - ScheduledTask retryTask = assertAndGetSingleCurrentTask(); - assertThat(retryTask.delay).isEqualTo(expectedRetryDelay); + TestScheduler.Task retryTask = assertAndGetSingleCurrentTask(); + assertThat(retryTask.getDelay()).isEqualTo(expectedRetryDelay); // Retry with another error clearInvocations(callback); - scheduledTasks.clear(); + scheduler.clearTasks(); requestSender.enqueueResponse(createFailedResponse(500)); retryTask.run(); verifySingleRequestSent(); verify(callback).onRequestFailed(any()); - ScheduledTask retryTask2 = assertAndGetSingleCurrentTask(); - assertThat(retryTask2.delay).isEqualTo(expectedRetryDelay); + TestScheduler.Task retryTask2 = assertAndGetSingleCurrentTask(); + assertThat(retryTask2.getDelay()).isEqualTo(expectedRetryDelay); // Retry with a success clearInvocations(callback); - scheduledTasks.clear(); + scheduler.clearTasks(); ServerToAgent serverToAgent = new ServerToAgent.Builder().build(); requestSender.enqueueResponse(createSuccessfulResponse(serverToAgent)); retryTask2.run(); @@ -283,16 +273,17 @@ private void verifyRetryDelayOnError( verify(periodicRequestDelay).reset(); verifySingleRequestSent(); verifyRequestSuccessCallback(serverToAgent); - assertThat(assertAndGetSingleCurrentTask().delay).isEqualTo(REGULAR_DELAY); + assertThat(assertAndGetSingleCurrentTask().getDelay()).isEqualTo(REGULAR_DELAY); } - private Request createRequestSupplier() { + private Request createRequest() { AgentToServer agentToServer = new AgentToServer.Builder().sequence_num(10).build(); requestSize = agentToServer.encodeByteString().size(); return Request.create(agentToServer); } - private ScheduledTask assertAndGetSingleCurrentTask() { + private TestScheduler.Task assertAndGetSingleCurrentTask() { + List scheduledTasks = scheduler.getScheduledTasks(); assertThat(scheduledTasks).hasSize(1); return scheduledTasks.get(0); } @@ -313,33 +304,6 @@ private void verifyRequestFailedCallback(int errorCode) { assertThat(captor.getValue().getMessage()).isEqualTo("Error message"); } - private ScheduledExecutorService createTestScheduleExecutorService() { - ScheduledExecutorService service = mock(); - - lenient() - .doAnswer( - invocation -> { - Runnable runnable = invocation.getArgument(0); - runnable.run(); - return null; - }) - .when(service) - .execute(any()); - - when(service.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) - .thenAnswer( - invocation -> { - ScheduledTask task = - new ScheduledTask(invocation.getArgument(0), invocation.getArgument(1)); - - scheduledTasks.add(task); - - return task; - }); - - return service; - } - private static HttpSender.Response createSuccessfulResponse(ServerToAgent serverToAgent) { return createSuccessfulResponse(serverToAgent.encodeByteString().toByteArray()); } @@ -370,32 +334,6 @@ private static PeriodicDelayWithSuggestion createPeriodicDelayWithSuggestionSupp return spy(new PeriodicDelayWithSuggestion(delay)); } - private static class PeriodicDelayWithSuggestion - implements PeriodicDelay, AcceptsDelaySuggestion { - private final Duration initialDelay; - private Duration currentDelay; - - private PeriodicDelayWithSuggestion(Duration initialDelay) { - this.initialDelay = initialDelay; - currentDelay = initialDelay; - } - - @Override - public void suggestDelay(Duration delay) { - currentDelay = delay; - } - - @Override - public Duration getNextDelay() { - return currentDelay; - } - - @Override - public void reset() { - currentDelay = initialDelay; - } - } - private static class TestHttpSender implements HttpSender { private final List requests = new ArrayList<>(); @@ -438,55 +376,4 @@ private RequestParams(int contentLength) { } } } - - private class ScheduledTask implements ScheduledFuture { - private final Runnable runnable; - private final Duration delay; - - public void run() { - get(); - } - - private ScheduledTask(Runnable runnable, long timeNanos) { - this.runnable = runnable; - this.delay = Duration.ofNanos(timeNanos); - } - - @Override - public long getDelay(@NotNull TimeUnit unit) { - throw new UnsupportedOperationException(); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return scheduledTasks.remove(this); - } - - @Override - public boolean isCancelled() { - throw new UnsupportedOperationException(); - } - - @Override - public boolean isDone() { - throw new UnsupportedOperationException(); - } - - @Override - public Object get() { - scheduledTasks.remove(this); - runnable.run(); - return null; - } - - @Override - public Object get(long timeout, @NotNull TimeUnit unit) { - throw new UnsupportedOperationException(); - } - - @Override - public int compareTo(@NotNull Delayed o) { - throw new UnsupportedOperationException(); - } - } } diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/PeriodicDelayWithSuggestion.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/PeriodicDelayWithSuggestion.java new file mode 100644 index 000000000..071067611 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/PeriodicDelayWithSuggestion.java @@ -0,0 +1,35 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; +import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; +import java.time.Duration; + +public class PeriodicDelayWithSuggestion implements PeriodicDelay, AcceptsDelaySuggestion { + private final Duration initialDelay; + private Duration currentDelay; + + public PeriodicDelayWithSuggestion(Duration initialDelay) { + this.initialDelay = initialDelay; + currentDelay = initialDelay; + } + + @Override + public void suggestDelay(Duration delay) { + currentDelay = delay; + } + + @Override + public Duration getNextDelay() { + return currentDelay; + } + + @Override + public void reset() { + currentDelay = initialDelay; + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/TestScheduler.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/TestScheduler.java new file mode 100644 index 000000000..18379a421 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/TestScheduler.java @@ -0,0 +1,120 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.Delayed; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.NotNull; + +public final class TestScheduler { + private final List tasks = new ArrayList<>(); + private final ScheduledExecutorService service = createTestScheduleExecutorService(); + + public ScheduledExecutorService getMockService() { + return service; + } + + public List getScheduledTasks() { + return Collections.unmodifiableList(tasks); + } + + public void clearTasks() { + tasks.clear(); + } + + private ScheduledExecutorService createTestScheduleExecutorService() { + ScheduledExecutorService service = mock(); + + lenient() + .doAnswer( + invocation -> { + Runnable runnable = invocation.getArgument(0); + runnable.run(); + return null; + }) + .when(service) + .execute(any()); + + lenient() + .when(service.schedule(any(Runnable.class), anyLong(), any(TimeUnit.class))) + .thenAnswer( + invocation -> { + Task task = new Task(invocation.getArgument(0), invocation.getArgument(1)); + + tasks.add(task); + + return task; + }); + + return service; + } + + public class Task implements ScheduledFuture { + private final Runnable runnable; + private final Duration delay; + + public void run() { + get(); + } + + private Task(Runnable runnable, long timeNanos) { + this.runnable = runnable; + this.delay = Duration.ofNanos(timeNanos); + } + + public Duration getDelay() { + return delay; + } + + @Override + public long getDelay(@NotNull TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return tasks.remove(this); + } + + @Override + public boolean isCancelled() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isDone() { + throw new UnsupportedOperationException(); + } + + @Override + public Object get() { + tasks.remove(this); + runnable.run(); + return null; + } + + @Override + public Object get(long timeout, @NotNull TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public int compareTo(@NotNull Delayed o) { + throw new UnsupportedOperationException(); + } + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java new file mode 100644 index 000000000..afca33fd0 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java @@ -0,0 +1,336 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import com.google.protobuf.CodedOutputStream; +import io.opentelemetry.opamp.client.internal.connectivity.websocket.WebSocket; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseError; +import io.opentelemetry.opamp.client.internal.response.Response; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.Duration; +import opamp.proto.AgentToServer; +import opamp.proto.RetryInfo; +import opamp.proto.ServerErrorResponse; +import opamp.proto.ServerErrorResponseType; +import opamp.proto.ServerToAgent; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class WebSocketRequestServiceTest { + @Mock private WebSocket webSocket; + @Mock private RequestService.Callback callback; + @Mock private PeriodicDelayWithSuggestion retryDelay; + private Request request; + private TestScheduler scheduler; + private WebSocketRequestService requestService; + private static final Duration INITIAL_RETRY_DELAY = Duration.ofSeconds(1); + + @BeforeEach + void setUp() { + lenient().when(retryDelay.getNextDelay()).thenReturn(INITIAL_RETRY_DELAY); + scheduler = new TestScheduler(); + requestService = new WebSocketRequestService(webSocket, retryDelay, scheduler.getMockService()); + } + + @Test + void verifySuccessfulStart() { + startService(); + verify(webSocket).open(requestService); + + // When opening successfully, notify callback + requestService.onOpen(); + verify(callback).onConnectionSuccess(); + verifyNoMoreInteractions(callback); + + // It shouldn't allow starting again + try { + startService(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("The service has already started"); + } + } + + @Test + void verifyFailedStart() { + startService(); + verify(webSocket).open(requestService); + + // When failing while opening, notify callback + Throwable t = mock(); + requestService.onFailure(t); + verify(retryDelay).reset(); + verify(callback).onConnectionFailed(t); + verifyNoMoreInteractions(callback); + + // Check connection retry is scheduled + assertThat(scheduler.getScheduledTasks()).hasSize(1); + assertThat(scheduler.getScheduledTasks().get(0).getDelay()).isEqualTo(INITIAL_RETRY_DELAY); + + // It shouldn't allow starting again + try { + startService(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("The service has already started"); + } + + // It shouldn't schedule more than one retry at a time + clearInvocations(retryDelay, callback); + requestService.onFailure(t); + verify(callback).onConnectionFailed(t); + verifyNoInteractions(retryDelay); + verifyNoMoreInteractions(callback); + assertThat(scheduler.getScheduledTasks()).hasSize(1); + + // Execute retry with new delay + clearInvocations(webSocket, callback); + when(retryDelay.getNextDelay()).thenReturn(Duration.ofSeconds(5)); + scheduler.getScheduledTasks().get(0).run(); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + verify(webSocket).open(requestService); + + // Fail again + requestService.onFailure(t); + verify(retryDelay, never()).reset(); + verify(callback).onConnectionFailed(t); + + // A new retry has been scheduled + assertThat(scheduler.getScheduledTasks()).hasSize(1); + assertThat(scheduler.getScheduledTasks().get(0).getDelay()).isEqualTo(Duration.ofSeconds(5)); + + // Execute retry again + clearInvocations(webSocket, callback); + scheduler.getScheduledTasks().get(0).run(); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + verify(webSocket).open(requestService); + + // Succeed + requestService.onOpen(); + verify(callback).onConnectionSuccess(); + verifyNoMoreInteractions(callback); + + // Fail at some point + clearInvocations(callback); + requestService.onFailure(t); + verify(callback).onConnectionFailed(t); + verifyNoMoreInteractions(callback); + verify(retryDelay).reset(); + assertThat(scheduler.getScheduledTasks()).hasSize(1); + } + + @Test + void verifySendRequest() { + // Validate when not running + try { + requestService.sendRequest(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("The service is not running"); + } + + startService(); + + // Successful send + when(webSocket.send(any())).thenReturn(true); + requestService.sendRequest(); + verify(webSocket).send(getExpectedOutgoingBytes()); + + // Check there are no pending requests + clearInvocations(webSocket); + requestService.onOpen(); + verifyNoInteractions(webSocket); + + // Failed send + when(webSocket.send(any())).thenReturn(false); + requestService.sendRequest(); + clearInvocations(webSocket); + + // Check pending request + when(webSocket.send(any())).thenReturn(true); + requestService.onOpen(); + verify(webSocket).send(getExpectedOutgoingBytes()); + } + + @Test + void verifyOnMessage() { + startService(); + + // Successful message + ServerToAgent serverToAgent = new ServerToAgent.Builder().build(); + requestService.onMessage(createServerToAgentPayload(serverToAgent)); + verify(callback).onRequestSuccess(Response.create(serverToAgent)); + verifyNoMoreInteractions(callback); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + + // Regular error message + ArgumentCaptor throwableCaptor = ArgumentCaptor.forClass(Throwable.class); + clearInvocations(callback); + serverToAgent = + new ServerToAgent.Builder() + .error_response(new ServerErrorResponse.Builder().error_message("A message").build()) + .build(); + requestService.onMessage(createServerToAgentPayload(serverToAgent)); + verify(callback).onRequestFailed(throwableCaptor.capture()); + verifyNoMoreInteractions(callback); + OpampServerResponseError error = (OpampServerResponseError) throwableCaptor.getValue(); + assertThat(error.getMessage()).isEqualTo("A message"); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + + // Error message with unavailable status + clearInvocations(callback); + serverToAgent = + new ServerToAgent.Builder() + .error_response( + new ServerErrorResponse.Builder() + .type(ServerErrorResponseType.ServerErrorResponseType_Unavailable) + .error_message("Try later") + .build()) + .build(); + requestService.onMessage(createServerToAgentPayload(serverToAgent)); + verify(callback).onRequestFailed(throwableCaptor.capture()); + verifyNoMoreInteractions(callback); + OpampServerResponseError unavailableError = + (OpampServerResponseError) throwableCaptor.getValue(); + assertThat(unavailableError.getMessage()).isEqualTo("Try later"); + assertThat(scheduler.getScheduledTasks()).hasSize(1); + verify(retryDelay, never()).suggestDelay(any()); + + // Reset scheduled retry + scheduler.getScheduledTasks().get(0).run(); + requestService.onOpen(); + + // Error message with unavailable status and suggested delay + Duration suggestedDelay = Duration.ofSeconds(10); + clearInvocations(callback, retryDelay); + serverToAgent = + new ServerToAgent.Builder() + .error_response( + new ServerErrorResponse.Builder() + .type(ServerErrorResponseType.ServerErrorResponseType_Unavailable) + .retry_info( + new RetryInfo.Builder() + .retry_after_nanoseconds(suggestedDelay.toNanos()) + .build()) + .build()) + .build(); + requestService.onMessage(createServerToAgentPayload(serverToAgent)); + verify(callback).onRequestFailed(throwableCaptor.capture()); + verifyNoMoreInteractions(callback); + OpampServerResponseError unavailableErrorWithSuggestedDelay = + (OpampServerResponseError) throwableCaptor.getValue(); + assertThat(unavailableErrorWithSuggestedDelay.getMessage()).isEmpty(); + assertThat(scheduler.getScheduledTasks()).hasSize(1); + verify(retryDelay).suggestDelay(suggestedDelay); + } + + @Test + void verifyStop() { + startService(); + + requestService.stop(); + + InOrder inOrder = inOrder(webSocket); + inOrder.verify(webSocket).send(getExpectedOutgoingBytes()); + inOrder.verify(webSocket).close(1000, null); + verify(scheduler.getMockService()).shutdown(); + + // If something fails afterward, no retry should get scheduled. + requestService.onFailure(mock()); + verifyNoInteractions(retryDelay); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + + // If onClosed is called afterward, no retry should get scheduled. + requestService.onClosed(); + verifyNoInteractions(retryDelay); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + + // If a new message with a server unavailable error arrives afterward, no retry should get + // scheduled. + ServerToAgent serverToAgent = + new ServerToAgent.Builder() + .error_response( + new ServerErrorResponse.Builder() + .type(ServerErrorResponseType.ServerErrorResponseType_Unavailable) + .build()) + .build(); + requestService.onMessage(createServerToAgentPayload(serverToAgent)); + verifyNoInteractions(retryDelay); + assertThat(scheduler.getScheduledTasks()).isEmpty(); + + // Requests cannot get enqueued afterward. + try { + requestService.sendRequest(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("This service is already stopped"); + } + + // The service cannot get restarted afterward. + try { + startService(); + fail(); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("This service is already stopped"); + } + } + + private byte[] getExpectedOutgoingBytes() { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + CodedOutputStream codedOutput = CodedOutputStream.newInstance(outputStream); + codedOutput.writeUInt64NoTag(0); + byte[] payload = request.getAgentToServer().encode(); + codedOutput.writeRawBytes(payload); + codedOutput.flush(); + return outputStream.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private static byte[] createServerToAgentPayload(ServerToAgent serverToAgent) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + CodedOutputStream codedOutput = CodedOutputStream.newInstance(outputStream); + codedOutput.writeUInt64NoTag(0); + codedOutput.writeRawBytes(serverToAgent.encode()); + codedOutput.flush(); + return outputStream.toByteArray(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void startService() { + requestService.start(callback, this::createRequest); + } + + private Request createRequest() { + AgentToServer agentToServer = new AgentToServer.Builder().sequence_num(10).build(); + request = Request.create(agentToServer); + return request; + } +} From 6a3094f18a012aca46e8d812eb78289705b47d0e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sat, 12 Jul 2025 16:47:12 -0700 Subject: [PATCH 123/371] More link check improvements (#2010) --- .github/scripts/.lychee-relative.toml | 7 ---- ...ies.dockerfile => dependencies.Dockerfile} | 0 .github/scripts/link-check.sh | 32 +++++++++---------- .../{.lychee.toml => lychee-config.toml} | 0 .github/workflows/reusable-link-check.yml | 2 +- 5 files changed, 16 insertions(+), 25 deletions(-) delete mode 100644 .github/scripts/.lychee-relative.toml rename .github/scripts/{dependencies.dockerfile => dependencies.Dockerfile} (100%) rename .github/scripts/{.lychee.toml => lychee-config.toml} (100%) diff --git a/.github/scripts/.lychee-relative.toml b/.github/scripts/.lychee-relative.toml deleted file mode 100644 index 03124f675..000000000 --- a/.github/scripts/.lychee-relative.toml +++ /dev/null @@ -1,7 +0,0 @@ -# Check link anchors -include_fragments = true - -# For relative links only, we exclude all external URLs -exclude = [ - "^https?://.*", -] diff --git a/.github/scripts/dependencies.dockerfile b/.github/scripts/dependencies.Dockerfile similarity index 100% rename from .github/scripts/dependencies.dockerfile rename to .github/scripts/dependencies.Dockerfile diff --git a/.github/scripts/link-check.sh b/.github/scripts/link-check.sh index f6d3ec7e7..7e6b6910a 100755 --- a/.github/scripts/link-check.sh +++ b/.github/scripts/link-check.sh @@ -6,21 +6,21 @@ export MSYS_NO_PATHCONV=1 # for Git Bash on Windows SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" ROOT_DIR="$SCRIPT_DIR/../.." -DEPENDENCIES_DOCKERFILE="$SCRIPT_DIR/dependencies.dockerfile" +DEPENDENCIES_DOCKERFILE="$SCRIPT_DIR/dependencies.Dockerfile" # Parse command line arguments -RELATIVE_ONLY=false -MODIFIED_FILES="" +LOCAL_LINKS_ONLY=false +TARGET="" while [[ $# -gt 0 ]]; do case $1 in - --relative-only) - RELATIVE_ONLY=true + --local-links-only) + LOCAL_LINKS_ONLY=true shift ;; *) # Treat any other arguments as file paths - MODIFIED_FILES="$MODIFIED_FILES $1" + TARGET="$TARGET $1" shift ;; esac @@ -29,26 +29,24 @@ done # Extract lychee version from dependencies.dockerfile LYCHEE_VERSION=$(grep "FROM lycheeverse/lychee:" "$DEPENDENCIES_DOCKERFILE" | sed 's/.*FROM lycheeverse\/lychee:\([^ ]*\).*/\1/') -# Determine target files/directories and config file -TARGET="." -LYCHEE_CONFIG=".github/scripts/.lychee.toml" - -if [[ "$RELATIVE_ONLY" == "true" ]]; then - LYCHEE_CONFIG=".github/scripts/.lychee-relative.toml" -fi - -if [[ -n "$MODIFIED_FILES" ]]; then - TARGET="$MODIFIED_FILES" +if [[ -z "$TARGET" ]]; then + TARGET="." fi # Build the lychee command with optional GitHub token -CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --config $LYCHEE_CONFIG" +CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --root-dir /data" # Add GitHub token if available if [[ -n "$GITHUB_TOKEN" ]]; then CMD="$CMD --github-token $GITHUB_TOKEN" fi +if [[ "$LOCAL_LINKS_ONLY" == "true" ]]; then + CMD="$CMD --scheme file --include-fragments" +else + CMD="$CMD --config .github/scripts/lychee-config.toml" +fi + CMD="$CMD $TARGET" # Determine if we should allocate a TTY diff --git a/.github/scripts/.lychee.toml b/.github/scripts/lychee-config.toml similarity index 100% rename from .github/scripts/.lychee.toml rename to .github/scripts/lychee-config.toml diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 6e45185c7..961a078f8 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -18,7 +18,7 @@ jobs: if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} - run: ./.github/scripts/link-check.sh --relative-only + run: ./.github/scripts/link-check.sh --local-links-only - name: Get modified files if: github.event_name == 'pull_request' From 36156f4d2ea919c00167736edfad01f7802a52c1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:05:49 +0300 Subject: [PATCH 124/371] fix(deps): update errorprone packages to v2.40.0 (#2024) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 0df38930b..de344216e 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -26,8 +26,8 @@ dependencies { api("com.google.auto.service:auto-service-annotations:1.1.1") api("com.google.auto.value:auto-value:1.11.0") api("com.google.auto.value:auto-value-annotations:1.11.0") - api("com.google.errorprone:error_prone_annotations:2.39.0") - api("com.google.errorprone:error_prone_core:2.39.0") + api("com.google.errorprone:error_prone_annotations:2.40.0") + api("com.google.errorprone:error_prone_core:2.40.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") api("io.prometheus:simpleclient:0.16.0") From a820cad474a13aab5b3d0a9b360a304d7bf4722f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 06:06:37 +0000 Subject: [PATCH 125/371] fix(deps): update dependency com.squareup.okhttp3:okhttp to v5.1.0 (#2023) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index de344216e..7bab5f4b8 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -44,7 +44,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.squareup.okhttp3:okhttp:5.0.0") + api("com.squareup.okhttp3:okhttp:5.1.0") api("com.uber.nullaway:nullaway:0.12.7") api("org.assertj:assertj-core:3.27.3") api("org.awaitility:awaitility:4.3.0") From 1773b8f778ea82fed6c0dbd7611225259dcb180d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 09:41:37 +0300 Subject: [PATCH 126/371] fix(deps): update spotless packages to v7.1.0 (#2025) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 1ab367234..a08eb9392 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "7.0.4" + id("com.diffplug.spotless") version "7.1.0" } repositories { @@ -12,7 +12,7 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:7.0.4") + implementation("com.diffplug.spotless:spotless-plugin-gradle:7.1.0") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.3") From 5d702ed0d3cb9511b488fa2c5fffe29da2a7badf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 14 Jul 2025 06:42:15 +0000 Subject: [PATCH 127/371] fix(deps): update all patch versions to v1.15.2 (#2026) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- micrometer-meter-provider/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index a9f13addc..9758a7415 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.15.1") + testImplementation("io.micrometer:micrometer-core:1.15.2") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.15.1") + implementation("io.micrometer:micrometer-registry-prometheus:1.15.2") } } } From e81ef92e8fe8c7df06de2c072c2e1eb3911cc45c Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Thu, 17 Jul 2025 17:41:59 +0100 Subject: [PATCH 128/371] refactor ConsistentFixedThresholdSampler to setup for dynamic alternative (#2018) --- .../ConsistentFixedThresholdSampler.java | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java index f2e92651c..82011e7a0 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java @@ -6,6 +6,7 @@ package io.opentelemetry.contrib.sampler.consistent56; import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateSamplingProbability; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.checkThreshold; import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; @@ -22,9 +23,20 @@ public class ConsistentFixedThresholdSampler extends ConsistentSampler { private final String description; protected ConsistentFixedThresholdSampler(long threshold) { + this.threshold = getThreshold(threshold); + this.description = getThresholdDescription(threshold); + } + + protected ConsistentFixedThresholdSampler(double samplingProbability) { + this(calculateThreshold(samplingProbability)); + } + + private static long getThreshold(long threshold) { checkThreshold(threshold); - this.threshold = threshold; + return threshold; + } + private static String getThresholdDescription(long threshold) { String thresholdString; if (threshold == getMaxThreshold()) { thresholdString = "max"; @@ -35,12 +47,11 @@ protected ConsistentFixedThresholdSampler(long threshold) { .toString(); } - this.description = - "ConsistentFixedThresholdSampler{threshold=" - + thresholdString - + ", sampling probability=" - + calculateSamplingProbability(threshold) - + "}"; + return "ConsistentFixedThresholdSampler{threshold=" + + thresholdString + + ", sampling probability=" + + calculateSamplingProbability(threshold) + + "}"; } @Override From 754211b2ae73cfdff262a784cced8b211d58c41f Mon Sep 17 00:00:00 2001 From: Peter Findeisen Date: Thu, 17 Jul 2025 09:58:07 -0700 Subject: [PATCH 129/371] 2007_ConsistentRateLimitingSampler (#2022) Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- .../consistent56/ConsistentSampler.java | 15 +- .../ConsistentRateLimitingSamplerTest.java | 198 ++++++++++++++++++ 2 files changed, 210 insertions(+), 3 deletions(-) diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java index 1b2cedf08..8ac616963 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java @@ -186,10 +186,19 @@ public final SamplingResult shouldSample( boolean isSampled; boolean isAdjustedCountCorrect; if (isValidThreshold(threshold)) { - long randomness = getRandomness(otelTraceState, traceId); - isSampled = threshold <= randomness; isAdjustedCountCorrect = intent.isAdjustedCountReliable(); - } else { // DROP + // determine the randomness value to use + long randomness; + if (isAdjustedCountCorrect) { + randomness = getRandomness(otelTraceState, traceId); + } else { + // We cannot assume any particular distribution of the provided trace randomness, + // because the sampling decision may depend directly or indirectly on the randomness value; + // however, we still want to sample with probability corresponding to the obtained threshold + randomness = RandomValueGenerators.getDefault().generate(traceId); + } + isSampled = threshold <= randomness; + } else { // invalid threshold, DROP isSampled = false; isAdjustedCountCorrect = false; } diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java index cc56df1ef..d5cb6b640 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSamplerTest.java @@ -10,7 +10,11 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; import io.opentelemetry.context.Context; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; @@ -357,6 +361,200 @@ void testProportionalBehavior() { .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); } + @Test + void testUnstableDelegate() { + // Assume there are 10,000 spans/s and the delegate samples 50% of them with probability 100%, + // and unconditionally rejects the rest. + // + // Now, if we do not want to sample more than 1000 spans/s overall, the rate limiting + // sampler should calculate the effective threshold correctly. + + double targetSpansPerSecondLimit = 1000; + double adaptationTimeSeconds = 5; + + Composable delegate = + new CoinFlipSampler(ConsistentSampler.alwaysOff(), ConsistentSampler.alwaysOn()); + + ConsistentSampler sampler = + ConsistentSampler.rateLimited( + delegate, targetSpansPerSecondLimit, adaptationTimeSeconds, nanoTimeSupplier); + + long averageRequestRatePerSecond = 10000; + int numSpans = 1000000; + + List spanSampledNanos = new ArrayList<>(); + + for (int i = 0; i < numSpans; ++i) { + advanceTime(randomInterval(averageRequestRatePerSecond)); + SamplingResult samplingResult = + sampler.shouldSample( + parentContext, + generateRandomTraceId(random), + name, + spanKind, + attributes, + parentLinks); + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision())) { + spanSampledNanos.add(getCurrentTimeNanos()); + } + } + + long timeNow = nanoTime[0]; + long numSampledSpansInLast5Seconds = + spanSampledNanos.stream().filter(x -> x > timeNow - 5000000000L && x <= timeNow).count(); + + assertThat(numSampledSpansInLast5Seconds / 5.) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(5)); + } + + @Test + void testLegacyCase() { + // This test makes sure that the issue + // https://github.com/open-telemetry/opentelemetry-java-contrib/issues/2007 + // is resolved. + + long averageRequestRatePerSecond = 10000; + + // Assume the following setup: + // The root span is sampled by the legacy sampler AlwaysOn. + // One of its descendant spans, which we will call "parent" span, is sampled with + // stage1: ConsistentRateLimitingSampler(ConsistentParentBasedSampler, 5000/s). + // This will sample approximately 50% of the spans. + + // Its "child" is similarly sampled by + // stage2: ConsistentRateLimitingSampler(ConsistentParentBasedSampler, 2500/s). + + // This sampler will generate the same output as the root span described above: + // - the threshold will be 0, so all spans will be sampled + // - isAdjustedCountReliable will be false + // - there will be no threshold in TraceState, but the sampling flag will be set + Composable mockRootSampler = new LegacyLikeComposable(ConsistentSampler.alwaysOn()); + + double targetSpansPerSecondLimit = 2500; // for stage2 + double adaptationTimeSeconds = 5; + + // The sampler for "parent" spans + ConsistentSampler stage1 = + ConsistentSampler.rateLimited( + mockRootSampler, + 2 * targetSpansPerSecondLimit, + adaptationTimeSeconds, + nanoTimeSupplier); + + // The sampler for "child" spans (it will never see root spans) + ConsistentSampler stage2 = + ConsistentSampler.rateLimited( + ConsistentSampler.parentBased(ConsistentSampler.alwaysOff()), + targetSpansPerSecondLimit, + adaptationTimeSeconds, + nanoTimeSupplier); + + int numSpans = 1000000; + int stage1SampledCount = 0; + int stage2SampledCount = 0; + + for (int i = 0; i < numSpans; ++i) { + advanceTime(randomInterval(averageRequestRatePerSecond)); + String traceId = generateRandomTraceId(random); + + // Stage 1 sampling, the "parent" + SamplingResult samplingResult1 = + stage1.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); + + boolean isSampled = SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult1.getDecision()); + if (isSampled) { + stage1SampledCount++; + } + + // Prepare the context for the child span, pass parent's TraceState to the child + Span parentSpan = Span.fromContext(parentContext); + SpanContext parentSpanContext = parentSpan.getSpanContext(); + TraceState parentSamplingTraceState = + samplingResult1.getUpdatedTraceState(parentSpanContext.getTraceState()); + + SpanContext childSpanContext = + SpanContext.create( + traceId, + "1000badbadbad000", + isSampled ? TraceFlags.getSampled() : TraceFlags.getDefault(), + parentSamplingTraceState); + Span childSpan = Span.wrap(childSpanContext); + Context childContext = childSpan.storeInContext(parentContext); + + // Stage 2 sampling, the "child" + SamplingResult samplingResult2 = + stage2.shouldSample(childContext, traceId, name, spanKind, attributes, parentLinks); + + if (SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult2.getDecision())) { + stage2SampledCount++; + } + } + + long timeNow = nanoTime[0]; + double duration = timeNow / 1000000000.0; // in seconds + assertThat(duration) + .isCloseTo(numSpans / (double) averageRequestRatePerSecond, Percentage.withPercentage(2)); + + assertThat(stage1SampledCount / duration) + .isCloseTo(2 * targetSpansPerSecondLimit, Percentage.withPercentage(2)); + + assertThat(stage2SampledCount / duration) + .isCloseTo(targetSpansPerSecondLimit, Percentage.withPercentage(2)); + } + + /* + * An auxiliary class used to simulate the behavior of a legacy (non consistent-probability) + * sampler, just for testing mixed environment + */ + static class LegacyLikeComposable implements Composable { + + private final Composable delegate; + + public LegacyLikeComposable(Composable delegate) { + this.delegate = delegate; + } + + @Override + public SamplingIntent getSamplingIntent( + Context parentContext, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + + SamplingIntent delegateIntent = + delegate.getSamplingIntent(parentContext, name, spanKind, attributes, parentLinks); + + return new SamplingIntent() { + @Override + public long getThreshold() { + return delegateIntent.getThreshold(); + } + + @Override + public boolean isAdjustedCountReliable() { + // Forcing "legacy" behavior, no threshold will be put into TraceState + return false; + } + + @Override + public Attributes getAttributes() { + return delegateIntent.getAttributes(); + } + + @Override + public TraceState updateTraceState(TraceState previousState) { + return delegateIntent.updateTraceState(previousState); + } + }; + } + + @Override + public String getDescription() { + return "LegacyLike(" + delegate.getDescription() + ")"; + } + } + @Test void testDescription() { From a5c597bd8a5a216be59eeb4f7b8fab7fc46da187 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:48:27 +0300 Subject: [PATCH 130/371] fix(deps): update spotless packages to v7.2.0 (#2039) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index a08eb9392..dbcb6f036 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "7.1.0" + id("com.diffplug.spotless") version "7.2.0" } repositories { @@ -12,7 +12,7 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:7.1.0") + implementation("com.diffplug.spotless:spotless-plugin-gradle:7.2.0") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.3") From c75dab73c1d3d62f498c306eaec1a5bfa76e4cf2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 09:48:51 +0300 Subject: [PATCH 131/371] fix(deps): update dependency com.fasterxml.jackson:jackson-bom to v2.19.2 (#2036) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7bab5f4b8..5b21d545d 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { // (the constraints section below doesn't have this issue, and will only show up // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) - api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.1")) + api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.2")) api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.31.1")) constraints { From dccc3f0554b43b4a7b845b824a2bdaa691e59618 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 07:30:41 +0000 Subject: [PATCH 132/371] fix(deps): update dependency org.junit:junit-bom to v5.13.4 (#2040) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 7df7e3512..4f55b43b5 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -135,7 +135,7 @@ testing { dependencies { implementation(project(project.path)) - implementation(enforcedPlatform("org.junit:junit-bom:5.13.3")) + implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) From 850933fe2b9d69f5a63364474d389a00074a80a2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 21 Jul 2025 13:10:03 +0200 Subject: [PATCH 133/371] update otel bom (#2042) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../BaggageProcessorCustomizerTest.java | 81 +++++++++--------- dependencyManagement/build.gradle.kts | 2 +- ...toConfigurationCustomizerProviderTest.java | 85 ++++++++++--------- 3 files changed, 84 insertions(+), 84 deletions(-) diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java index 9dcb9a4a7..bfa8835ae 100644 --- a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java @@ -12,13 +12,12 @@ import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.baggage.Baggage; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; -import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; -import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.logs.ConfigurableLogRecordExporterProvider; @@ -113,45 +112,45 @@ private static OpenTelemetrySdk getOpenTelemetrySdk( "none", "otel.logs.exporter", MEMORY_EXPORTER)) - .addPropertiesSupplier(() -> properties); - AutoConfigureUtil.setComponentLoader( - sdkBuilder, - new ComponentLoader() { - @SuppressWarnings("unchecked") - @Override - public List load(Class spiClass) { - if (spiClass == ConfigurableSpanExporterProvider.class) { - return Collections.singletonList( - (T) - new ConfigurableSpanExporterProvider() { - @Override - public SpanExporter createExporter(ConfigProperties configProperties) { - return spanExporter; - } - - @Override - public String getName() { - return MEMORY_EXPORTER; - } - }); - } else if (spiClass == ConfigurableLogRecordExporterProvider.class) { - return Collections.singletonList( - (T) - new ConfigurableLogRecordExporterProvider() { - @Override - public LogRecordExporter createExporter(ConfigProperties configProperties) { - return logRecordExporter; - } - - @Override - public String getName() { - return MEMORY_EXPORTER; - } - }); - } - return spiHelper.load(spiClass); - } - }); + .addPropertiesSupplier(() -> properties) + .setComponentLoader( + new ComponentLoader() { + @Override + public List load(Class spiClass) { + if (spiClass == ConfigurableSpanExporterProvider.class) { + return Collections.singletonList( + spiClass.cast( + new ConfigurableSpanExporterProvider() { + @Override + public SpanExporter createExporter( + ConfigProperties configProperties) { + return spanExporter; + } + + @Override + public String getName() { + return MEMORY_EXPORTER; + } + })); + } else if (spiClass == ConfigurableLogRecordExporterProvider.class) { + return Collections.singletonList( + spiClass.cast( + new ConfigurableLogRecordExporterProvider() { + @Override + public LogRecordExporter createExporter( + ConfigProperties configProperties) { + return logRecordExporter; + } + + @Override + public String getName() { + return MEMORY_EXPORTER; + } + })); + } + return spiHelper.load(spiClass); + } + }); return sdkBuilder.build().getOpenTelemetrySdk(); } diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 5b21d545d..3001ba0aa 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.17.1-alpha" +val otelInstrumentationVersion = "2.18.0-alpha" val semconvVersion = "1.34.0" javaPlatform { diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 6cc1797f0..4fb687925 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -23,6 +23,7 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.trace.Span; +import io.opentelemetry.common.ComponentLoader; import io.opentelemetry.context.Scope; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; @@ -35,8 +36,6 @@ import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder; -import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil; -import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader; import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; @@ -960,46 +959,48 @@ private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( SpiHelper spiHelper = SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader()); AutoConfiguredOpenTelemetrySdkBuilder builder = - AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> customOtelProperties); - AutoConfigureUtil.setComponentLoader( - builder, - new ComponentLoader() { - @SuppressWarnings("unchecked") - @Override - public List load(Class spiClass) { - if (spiClass == ConfigurableSpanExporterProvider.class) { - return Collections.singletonList( - (T) - new ConfigurableSpanExporterProvider() { - @Override - public SpanExporter createExporter(ConfigProperties configProperties) { - return spanExporter; - } - - @Override - public String getName() { - return "otlp"; - } - }); - } - if (spiClass == ConfigurableMetricExporterProvider.class) { - return Collections.singletonList( - (T) - new ConfigurableMetricExporterProvider() { - @Override - public MetricExporter createExporter(ConfigProperties configProperties) { - return metricExporter; - } - - @Override - public String getName() { - return "otlp"; - } - }); - } - return spiHelper.load(spiClass); - } - }); + AutoConfiguredOpenTelemetrySdk.builder() + .addPropertiesSupplier(() -> customOtelProperties) + .setComponentLoader( + new ComponentLoader() { + @Override + public List load(Class spiClass) { + if (spiClass == ConfigurableSpanExporterProvider.class) { + return Collections.singletonList( + spiClass.cast( + new ConfigurableSpanExporterProvider() { + @Override + public SpanExporter createExporter( + ConfigProperties configProperties) { + return spanExporter; + } + + @Override + public String getName() { + return "otlp"; + } + })); + } + if (spiClass == ConfigurableMetricExporterProvider.class) { + return Collections.singletonList( + spiClass.cast( + new ConfigurableMetricExporterProvider() { + @Override + public MetricExporter createExporter( + ConfigProperties configProperties) { + return metricExporter; + } + + @Override + public String getName() { + return "otlp"; + } + })); + } + return spiHelper.load(spiClass); + } + }); + return builder.build().getOpenTelemetrySdk(); } From c4f12d90cfb74ec1efe919ac4f76300dd589de0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 14:13:57 +0300 Subject: [PATCH 134/371] chore(deps): update plugin com.gradle.develocity to v4.1 (#2037) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 27db34c41..bc4b5a1de 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.github.johnrengelman.shadow") version "8.1.1" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.0.2" + id("com.gradle.develocity") version "4.1" } } From f917dd1d9e9faa569970f6c0fd42956cc7e63a22 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 22 Jul 2025 03:54:03 +0200 Subject: [PATCH 135/371] jmx add link to jetty metrics for 2.18.0 (#2033) --- jmx-scraper/README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 72602705f..3518f9345 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -58,20 +58,20 @@ If there is a need to override existing ready-to-use metrics or to keep control Supported values for `otel.jmx.target.system` and support for `otel.jmx.target.source` and links to the metrics definitions: -| `otel.jmx.target.system` | description | `legacy` | `instrumentation` | -|--------------------------|-----------------------|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `activemq` | Apache ActiveMQ | [`activemq.yaml`](src/main/resources/activemq.yaml) | | -| `cassandra` | Apache Cassandra | [`cassandra.yaml`](src/main/resources/cassandra.yaml) | | -| `hbase` | Apache HBase | [`hbase.yaml`](src/main/resources/hbase.yaml) | | -| `hadoop` | Apache Hadoop | [`hadoop.yaml`](src/main/resources/hadoop.yaml) | | -| `jetty` | Eclipse Jetty | [`jetty.yaml`](src/main/resources/jetty.yaml) | | -| `jvm` | JVM runtime metrics | [`jvm.yaml`](src/main/resources/jvm.yaml) | [`jvm.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jvm.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jvm.md)) | -| `kafka` | Apache Kafka | [`kafka.yaml`](src/main/resources/kafka.yaml) | | -| `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | -| `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | -| `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | -| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | [`tomcat.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml) | -| `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | | +| `otel.jmx.target.system` | description | `legacy` | `instrumentation` | +|--------------------------|-----------------------|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `activemq` | Apache ActiveMQ | [`activemq.yaml`](src/main/resources/activemq.yaml) | | +| `cassandra` | Apache Cassandra | [`cassandra.yaml`](src/main/resources/cassandra.yaml) | | +| `hbase` | Apache HBase | [`hbase.yaml`](src/main/resources/hbase.yaml) | | +| `hadoop` | Apache Hadoop | [`hadoop.yaml`](src/main/resources/hadoop.yaml) | | +| `jetty` | Eclipse Jetty | [`jetty.yaml`](src/main/resources/jetty.yaml) | [`jetty.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jetty.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jetty.md)) | +| `jvm` | JVM runtime metrics | [`jvm.yaml`](src/main/resources/jvm.yaml) | [`jvm.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jvm.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jvm.md)) | +| `kafka` | Apache Kafka | [`kafka.yaml`](src/main/resources/kafka.yaml) | | +| `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | +| `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | +| `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | +| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | [`tomcat.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml) | +| `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | | The source of metrics definitions is controlled by `otel.jmx.target.source`: @@ -87,12 +87,12 @@ Setting the value of `otel.jmx.target.source` allows to fit the following use-ca The following [SDK configuration options](https://opentelemetry.io/docs/languages/java/configuration/#environment-variables-and-system-properties) are also relevant -| config option | default value | description | -|-------------------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `otel.metric.export.interval` | `1m` (1 minute) | metric export interval, also controls the JMX sampling interval | -| `otel.metrics.exporter` | `otlp` | comma-separated list of metrics exporters supported values are `otlp` and `logging`, additional values might be provided through extra libraries in the classpath | -| `otel.service.name` | | service name | -| `otel.resource.attributes` | | used to specify otel resource attributes, including service attributes. See [the sdk configuration](https://opentelemetry.io/docs/languages/java/configuration/#properties-resource) and [service attributes](https://opentelemetry.io/docs/specs/semconv/registry/attributes/service/) | +| config option | default value | description | +|-------------------------------|-----------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `otel.metric.export.interval` | `1m` (1 minute) | metric export interval, also controls the JMX sampling interval | +| `otel.metrics.exporter` | `otlp` | comma-separated list of metrics exporters supported values are `otlp` and `logging`, additional values might be provided through extra libraries in the classpath | +| `otel.service.name` | | service name | +| `otel.resource.attributes` | | used to specify otel resource attributes, including service attributes. See [the sdk configuration](https://opentelemetry.io/docs/languages/java/configuration/#properties-resource) and [service attributes](https://opentelemetry.io/docs/specs/semconv/registry/attributes/service/) | In addition to OpenTelemetry configuration, the following Java system properties can be provided through the command-line arguments, properties file or stdin and will be propagated to the JVM system properties: From 0f353e3b88736a37dc9d994d0377c80f1a6e3e26 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:07:42 -0700 Subject: [PATCH 136/371] fix(deps): update spotless packages to v7.2.1 (#2045) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index dbcb6f036..ba5b81ceb 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "7.2.0" + id("com.diffplug.spotless") version "7.2.1" } repositories { @@ -12,7 +12,7 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:7.2.0") + implementation("com.diffplug.spotless:spotless-plugin-gradle:7.2.1") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.3") From b35183938a759520b82e99433946468a13faed3a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 21 Jul 2025 19:08:03 -0700 Subject: [PATCH 137/371] fix(deps): update all patch versions (#2044) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- dependencyManagement/build.gradle.kts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index dfbca6b06..610e2feb4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL - uses: github/codeql-action/init@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/init@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/analyze@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c339c65ba..e0d440abd 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@181d5eefc20863364f96762470ba6f862bdef56b # v3.29.2 + uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 with: sarif_file: results.sarif diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 3001ba0aa..f206758a9 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.18.0-alpha" +val otelInstrumentationVersion = "2.18.1-alpha" val semconvVersion = "1.34.0" javaPlatform { From 28592055f97082208cb3a429aaf236fc484c39b0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 22 Jul 2025 04:08:47 +0200 Subject: [PATCH 138/371] Declarative config: Cloud component providers (#2014) --- aws-resources/build.gradle.kts | 2 + .../internal/AwsResourceDetector.java | 40 ++++++++++++++ ...toconfigure.spi.internal.ComponentProvider | 1 + .../ResourceComponentProviderTest.java | 24 +++++++++ azure-resources/build.gradle.kts | 2 + .../resource/AzureAksResourceProvider.java | 5 +- .../AzureAppServiceResourceProvider.java | 3 +- .../AzureContainersResourceProvider.java | 3 +- .../AzureFunctionsResourceProvider.java | 3 +- .../azure/resource/AzureResourceDetector.java | 53 +++++++++++++++++++ .../resource/AzureVmResourceProvider.java | 3 +- .../azure/resource/CloudResourceProvider.java | 8 +++ ...toconfigure.spi.internal.ComponentProvider | 1 + .../ResourceComponentProviderTest.java | 24 +++++++++ cloudfoundry-resources/build.gradle.kts | 2 + .../CloudFoundryResourceDetector.java | 28 ++++++++++ ...toconfigure.spi.internal.ComponentProvider | 1 + .../ResourceComponentProviderTest.java | 24 +++++++++ gcp-resources/build.gradle.kts | 2 + .../internal/GcpResourceDetector.java | 32 +++++++++++ ...toconfigure.spi.internal.ComponentProvider | 1 + .../ResourceComponentProviderTest.java | 24 +++++++++ maven-extension/build.gradle.kts | 2 + .../resources/MavenResourceDetector.java | 28 ++++++++++ .../resources/MavenResourceProvider.java | 4 ++ ...toconfigure.spi.internal.ComponentProvider | 1 + .../ResourceComponentProviderTest.java | 24 +++++++++ resource-providers/build.gradle.kts | 2 + .../AppServerResourceDetector.java | 31 +++++++++++ .../AppServerServiceNameProvider.java | 4 ++ .../ResourceComponentProviderTest.java | 24 +++++++++ 31 files changed, 395 insertions(+), 11 deletions(-) create mode 100644 aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/internal/AwsResourceDetector.java create mode 100644 aws-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java create mode 100644 azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java create mode 100644 azure-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java create mode 100644 cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java create mode 100644 cloudfoundry-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java create mode 100644 gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/internal/GcpResourceDetector.java create mode 100644 gcp-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/ResourceComponentProviderTest.java create mode 100644 maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceDetector.java create mode 100644 maven-extension/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider create mode 100644 maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java create mode 100644 resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java create mode 100644 resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/ResourceComponentProviderTest.java diff --git a/aws-resources/build.gradle.kts b/aws-resources/build.gradle.kts index ede0dad59..580ecb44f 100644 --- a/aws-resources/build.gradle.kts +++ b/aws-resources/build.gradle.kts @@ -9,6 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.aws.resource") dependencies { api("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") api("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry.semconv:opentelemetry-semconv") @@ -20,6 +21,7 @@ dependencies { implementation("com.squareup.okhttp3:okhttp") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("com.linecorp.armeria:armeria-junit5") diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/internal/AwsResourceDetector.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/internal/AwsResourceDetector.java new file mode 100644 index 000000000..ae4255570 --- /dev/null +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/internal/AwsResourceDetector.java @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.aws.resource.internal; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.contrib.aws.resource.BeanstalkResource; +import io.opentelemetry.contrib.aws.resource.Ec2Resource; +import io.opentelemetry.contrib.aws.resource.EcsResource; +import io.opentelemetry.contrib.aws.resource.EksResource; +import io.opentelemetry.contrib.aws.resource.LambdaResource; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; + +public class AwsResourceDetector implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "aws"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + ResourceBuilder builder = Resource.builder(); + builder.putAll(BeanstalkResource.get()); + builder.putAll(Ec2Resource.get()); + builder.putAll(EcsResource.get()); + builder.putAll(EksResource.get()); + builder.putAll(LambdaResource.get()); + return builder.build(); + } +} diff --git a/aws-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/aws-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..ea6d743f4 --- /dev/null +++ b/aws-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1 @@ +io.opentelemetry.contrib.aws.resource.internal.AwsResourceDetector diff --git a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java new file mode 100644 index 000000000..1534aca17 --- /dev/null +++ b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.aws.resource; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import org.junit.jupiter.api.Test; + +class ResourceComponentProviderTest { + + @Test + @SuppressWarnings("rawtypes") + void providerIsLoaded() { + Iterable providers = + ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) + .load(ComponentProvider.class); + assertThat(providers).extracting(ComponentProvider::getName).containsExactly("aws"); + } +} diff --git a/azure-resources/build.gradle.kts b/azure-resources/build.gradle.kts index 33af12d84..c17d14787 100644 --- a/azure-resources/build.gradle.kts +++ b/azure-resources/build.gradle.kts @@ -15,6 +15,7 @@ java { dependencies { api("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") api("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry.semconv:opentelemetry-semconv") @@ -26,6 +27,7 @@ dependencies { testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") // testImplementation("org.mockito:mockito-core") diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java index 4d3b92280..9823c6f9e 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java @@ -8,7 +8,6 @@ import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.CloudPlatformIncubatingValues.AZURE_AKS; import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.K8S_CLUSTER_NAME; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.Map; @@ -56,12 +55,12 @@ public AzureAksResourceProvider() { @Override public int order() { // run after the fast cloud resource providers that only check environment variables - // and before the AKS provider + // and before the VM provider return 100; } @Override - public Resource createResource(ConfigProperties configProperties) { + public Resource createResource() { if (environment.get(KUBERNETES_SERVICE_HOST) == null) { return Resource.empty(); } diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java index 71cf699e6..74ad96334 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java @@ -17,7 +17,6 @@ import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; import io.opentelemetry.api.internal.StringUtils; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.Map; @@ -60,7 +59,7 @@ public AzureAppServiceResourceProvider() { } @Override - public Resource createResource(ConfigProperties config) { + public Resource createResource() { return Resource.create(getAttributes()); } diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java index 2f641148f..63d0f4428 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java @@ -12,7 +12,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.Map; @@ -45,7 +44,7 @@ public AzureContainersResourceProvider() { } @Override - public Resource createResource(ConfigProperties config) { + public Resource createResource() { return Resource.create(getAttributes()); } diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java index 1b86c6212..e35fe95c4 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java @@ -15,7 +15,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.Map; @@ -47,7 +46,7 @@ public AzureFunctionsResourceProvider() { } @Override - public Resource createResource(ConfigProperties config) { + public Resource createResource() { return Resource.create(getAttributes()); } diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java new file mode 100644 index 000000000..556e1d60c --- /dev/null +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.azure.resource; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; + +public class AzureResourceDetector implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "azure"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + Builder builder = new Builder(); + builder.add(new AzureFunctionsResourceProvider()); + builder.add(new AzureAppServiceResourceProvider()); + builder.add(new AzureContainersResourceProvider()); + builder.addIfEmpty(new AzureAksResourceProvider()); + builder.addIfEmpty(new AzureVmResourceProvider()); + return builder.builder.build(); + } + + private static class Builder { + final ResourceBuilder builder = Resource.builder(); + int attributesCount = 0; + + private void add(CloudResourceProvider provider) { + Attributes attributes = provider.createResource().getAttributes(); + builder.putAll(attributes); + attributesCount += attributes.size(); + } + + private void addIfEmpty(CloudResourceProvider provider) { + if (attributesCount == 0) { + add(provider); + } + } + } +} diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java index 139b808d4..38c983b4a 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java @@ -22,7 +22,6 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.resources.Resource; import java.io.IOException; import java.util.HashMap; @@ -88,7 +87,7 @@ public int order() { } @Override - public Resource createResource(ConfigProperties config) { + public Resource createResource() { return client .get() .map(body -> parseMetadata(body, COMPUTE_MAPPING, AZURE_VM)) diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/CloudResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/CloudResourceProvider.java index 181a22889..3c7fcc862 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/CloudResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/CloudResourceProvider.java @@ -17,4 +17,12 @@ public abstract class CloudResourceProvider implements ConditionalResourceProvid public final boolean shouldApply(ConfigProperties config, Resource existing) { return existing.getAttribute(CLOUD_PROVIDER) == null; } + + @Override + public final Resource createResource(ConfigProperties config) { + // not using config in any providers + return createResource(); + } + + abstract Resource createResource(); } diff --git a/azure-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/azure-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..373780ff0 --- /dev/null +++ b/azure-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1 @@ +io.opentelemetry.contrib.azure.resource.AzureResourceDetector diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java new file mode 100644 index 000000000..f04e256ff --- /dev/null +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.azure.resource; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import org.junit.jupiter.api.Test; + +class ResourceComponentProviderTest { + + @Test + @SuppressWarnings("rawtypes") + void providerIsLoaded() { + Iterable providers = + ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) + .load(ComponentProvider.class); + assertThat(providers).extracting(ComponentProvider::getName).containsExactly("azure"); + } +} diff --git a/cloudfoundry-resources/build.gradle.kts b/cloudfoundry-resources/build.gradle.kts index e768f7389..3324504b5 100644 --- a/cloudfoundry-resources/build.gradle.kts +++ b/cloudfoundry-resources/build.gradle.kts @@ -9,6 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.cloudfoundry.resources") dependencies { api("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") api("io.opentelemetry:opentelemetry-sdk") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") @@ -19,6 +20,7 @@ dependencies { testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") } diff --git a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java new file mode 100644 index 000000000..02b9a6b09 --- /dev/null +++ b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.cloudfoundry.resources; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; + +public class CloudFoundryResourceDetector implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "cloud_foundry"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + return CloudFoundryResource.get(); + } +} diff --git a/cloudfoundry-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/cloudfoundry-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..96092ce3d --- /dev/null +++ b/cloudfoundry-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1 @@ +io.opentelemetry.contrib.cloudfoundry.resources.CloudFoundryResourceDetector diff --git a/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java b/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java new file mode 100644 index 000000000..2e1e434b2 --- /dev/null +++ b/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.cloudfoundry.resources; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import org.junit.jupiter.api.Test; + +class ResourceComponentProviderTest { + + @Test + @SuppressWarnings("rawtypes") + void providerIsLoaded() { + Iterable providers = + ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) + .load(ComponentProvider.class); + assertThat(providers).extracting(ComponentProvider::getName).containsExactly("cloud_foundry"); + } +} diff --git a/gcp-resources/build.gradle.kts b/gcp-resources/build.gradle.kts index 998c15870..cc227ed6e 100644 --- a/gcp-resources/build.gradle.kts +++ b/gcp-resources/build.gradle.kts @@ -9,6 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.gcp.resource") dependencies { api("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") api("io.opentelemetry:opentelemetry-sdk") // Provides GCP resource detection support @@ -21,6 +22,7 @@ dependencies { implementation("com.fasterxml.jackson.core:jackson-core") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("org.mockito:mockito-core") diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/internal/GcpResourceDetector.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/internal/GcpResourceDetector.java new file mode 100644 index 000000000..35adbeded --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/internal/GcpResourceDetector.java @@ -0,0 +1,32 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource.internal; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.contrib.gcp.resource.GCPResourceProvider; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.resources.ResourceBuilder; + +public class GcpResourceDetector implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "gcp"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + ResourceBuilder builder = Resource.builder(); + builder.putAll(new GCPResourceProvider().getAttributes()); + return builder.build(); + } +} diff --git a/gcp-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/gcp-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..e65bbc840 --- /dev/null +++ b/gcp-resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1 @@ +io.opentelemetry.contrib.gcp.resource.internal.GcpResourceDetector diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/ResourceComponentProviderTest.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/ResourceComponentProviderTest.java new file mode 100644 index 000000000..b80d6c427 --- /dev/null +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/ResourceComponentProviderTest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import org.junit.jupiter.api.Test; + +class ResourceComponentProviderTest { + + @Test + @SuppressWarnings("rawtypes") + void providerIsLoaded() { + Iterable providers = + ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) + .load(ComponentProvider.class); + assertThat(providers).extracting(ComponentProvider::getName).containsExactly("gcp"); + } +} diff --git a/maven-extension/build.gradle.kts b/maven-extension/build.gradle.kts index d14faa992..a2972d0ea 100644 --- a/maven-extension/build.gradle.kts +++ b/maven-extension/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { compileOnly("javax.inject:javax.inject:1") implementation("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") implementation("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-sdk-trace") implementation("io.opentelemetry:opentelemetry-sdk-metrics") @@ -32,6 +33,7 @@ dependencies { compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update, support older mvn versions compileOnly("org.slf4j:slf4j-api") + testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("org.apache.maven:maven-core:3.5.0") testImplementation("org.slf4j:slf4j-simple") } diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceDetector.java b/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceDetector.java new file mode 100644 index 000000000..47ff50759 --- /dev/null +++ b/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceDetector.java @@ -0,0 +1,28 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven.resources; + +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; + +public class MavenResourceDetector implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "maven"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + return MavenResourceProvider.create(); + } +} diff --git a/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceProvider.java b/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceProvider.java index 66e7b028b..9b75d11d0 100644 --- a/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceProvider.java +++ b/maven-extension/src/main/java/io/opentelemetry/maven/resources/MavenResourceProvider.java @@ -23,6 +23,10 @@ public class MavenResourceProvider implements ResourceProvider { @Override public Resource createResource(ConfigProperties config) { + return create(); + } + + static Resource create() { return Resource.builder() .put(ServiceAttributes.SERVICE_NAME, MavenOtelSemanticAttributes.SERVICE_NAME_VALUE) .put(ServiceAttributes.SERVICE_VERSION, getMavenRuntimeVersion()) diff --git a/maven-extension/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider b/maven-extension/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider new file mode 100644 index 000000000..baac89a6a --- /dev/null +++ b/maven-extension/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider @@ -0,0 +1 @@ +io.opentelemetry.maven.resources.MavenResourceDetector diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java new file mode 100644 index 000000000..e992f24da --- /dev/null +++ b/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.maven.resources; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import org.junit.jupiter.api.Test; + +class ResourceComponentProviderTest { + + @Test + @SuppressWarnings("rawtypes") + void providerIsLoaded() { + Iterable providers = + ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) + .load(ComponentProvider.class); + assertThat(providers).extracting(ComponentProvider::getName).contains("maven"); + } +} diff --git a/resource-providers/build.gradle.kts b/resource-providers/build.gradle.kts index 1dc20be51..4dc0a28d5 100644 --- a/resource-providers/build.gradle.kts +++ b/resource-providers/build.gradle.kts @@ -11,6 +11,7 @@ dependencies { compileOnly("com.google.auto.service:auto-service") compileOnly("io.opentelemetry:opentelemetry-api") + compileOnly("io.opentelemetry:opentelemetry-api-incubator") compileOnly("io.opentelemetry:opentelemetry-sdk") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") @@ -18,4 +19,5 @@ dependencies { testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") testImplementation("com.google.auto.service:auto-service") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-api-incubator") } diff --git a/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java b/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java new file mode 100644 index 000000000..7d5d399c4 --- /dev/null +++ b/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.resourceproviders; + +import com.google.auto.service.AutoService; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.resources.Resource; + +@SuppressWarnings("rawtypes") +@AutoService(ComponentProvider.class) +public class AppServerResourceDetector implements ComponentProvider { + + @Override + public Class getType() { + return Resource.class; + } + + @Override + public String getName() { + return "app_server"; + } + + @Override + public Resource create(DeclarativeConfigProperties config) { + return new AppServerServiceNameProvider().create(); + } +} diff --git a/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerServiceNameProvider.java b/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerServiceNameProvider.java index 505dbef84..5ce7196f2 100644 --- a/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerServiceNameProvider.java +++ b/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerServiceNameProvider.java @@ -38,6 +38,10 @@ public AppServerServiceNameProvider() { @Override public Resource createResource(ConfigProperties config) { + return create(); + } + + Resource create() { String serviceName = detectServiceName(); if (serviceName == null) { logger.log( diff --git a/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/ResourceComponentProviderTest.java b/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/ResourceComponentProviderTest.java new file mode 100644 index 000000000..91ea7b216 --- /dev/null +++ b/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/ResourceComponentProviderTest.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.resourceproviders; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import org.junit.jupiter.api.Test; + +class ResourceComponentProviderTest { + + @Test + @SuppressWarnings("rawtypes") + void providerIsLoaded() { + Iterable providers = + ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) + .load(ComponentProvider.class); + assertThat(providers).extracting(ComponentProvider::getName).contains("app_server"); + } +} From 95c30f24a943ac104e1b89c4be04348ad2ae3064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:56:44 +0200 Subject: [PATCH 139/371] Opamp client implementation (#2021) --- dependencyManagement/build.gradle.kts | 3 +- opamp-client/build.gradle.kts | 3 + .../opamp/client/internal/OpampClient.java | 23 +- .../client/internal/OpampClientBuilder.java | 492 ++++++++++++++++++ .../connectivity/http/OkHttpSender.java | 19 +- .../connectivity/http/package-info.java | 9 + .../client/internal/impl/OpampClientImpl.java | 268 ++++++++++ .../internal/impl/OpampClientState.java | 58 +++ .../impl/recipe/AgentToServerAppenders.java | 73 +++ .../internal/impl/recipe/RecipeManager.java | 86 +++ .../internal/impl/recipe/RequestRecipe.java | 25 + .../appenders/AgentDescriptionAppender.java | 31 ++ .../appenders/AgentDisconnectAppender.java | 27 + .../appenders/AgentToServerAppender.java | 24 + .../appenders/CapabilitiesAppender.java | 30 ++ .../appenders/EffectiveConfigAppender.java | 31 ++ .../impl/recipe/appenders/FlagsAppender.java | 30 ++ .../recipe/appenders/InstanceUidAppender.java | 31 ++ .../appenders/RemoteConfigStatusAppender.java | 31 ++ .../appenders/SequenceNumberAppender.java | 30 ++ .../opamp/client/internal/package-info.java | 9 + .../opamp/client/internal/request/Field.java | 25 + .../request/service/HttpRequestService.java | 6 +- .../service/WebSocketRequestService.java | 9 +- .../response/OpampServerResponseError.java | 19 - .../OpampServerResponseException.java | 29 ++ .../client/internal/state/InMemoryState.java | 38 ++ .../internal/state/ObservableState.java | 49 ++ .../opamp/client/internal/state/State.java | 108 ++++ .../internal/impl/OpampClientImplTest.java | 485 +++++++++++++++++ .../internal/impl/OpampClientStateTest.java | 43 ++ .../recipe/AgentToServerAppendersTest.java | 53 ++ .../impl/recipe/RecipeManagerTest.java | 66 +++ .../service/WebSocketRequestServiceTest.java | 12 +- 34 files changed, 2217 insertions(+), 58 deletions(-) create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/package-info.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientState.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppenders.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManager.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RequestRecipe.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDescriptionAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDisconnectAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentToServerAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/CapabilitiesAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/EffectiveConfigAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/FlagsAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/InstanceUidAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/RemoteConfigStatusAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/SequenceNumberAppender.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/package-info.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Field.java delete mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseException.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/ObservableState.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/State.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientStateTest.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppendersTest.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManagerTest.java diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index f206758a9..ea0541ca8 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.2")) api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.31.1")) + api(enforcedPlatform("com.squareup.okhttp3:okhttp-bom:5.1.0")) constraints { api("io.opentelemetry.semconv:opentelemetry-semconv:${semconvVersion}") @@ -44,7 +45,6 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.squareup.okhttp3:okhttp:5.1.0") api("com.uber.nullaway:nullaway:0.12.7") api("org.assertj:assertj-core:3.27.3") api("org.awaitility:awaitility:4.3.0") @@ -58,5 +58,6 @@ dependencies { api("tools.profiler:async-profiler:4.0") api("com.blogspot.mydailyjava:weak-lock-free:0.18") api("org.agrona:agrona:1.22.0") + api("com.github.f4b6a3:uuid-creator:6.0.0") } } diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index e0a7f8431..e41d1fff9 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -13,10 +13,13 @@ otelJava.moduleName.set("io.opentelemetry.contrib.opamp.client") dependencies { implementation("com.squareup.okhttp3:okhttp") + implementation("com.github.f4b6a3:uuid-creator") annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") testImplementation("org.mockito:mockito-inline") testImplementation("com.google.protobuf:protobuf-java-util") + testImplementation("com.squareup.okhttp3:mockwebserver3") + testImplementation("com.squareup.okhttp3:mockwebserver3-junit5") } val opampProtos = tasks.register("opampProtoDownload", download) diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java index ae12aa2e4..c835c6f55 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java @@ -6,17 +6,21 @@ package io.opentelemetry.opamp.client.internal; import io.opentelemetry.opamp.client.internal.response.MessageData; +import javax.annotation.Nullable; import opamp.proto.AgentDescription; import opamp.proto.RemoteConfigStatus; import opamp.proto.ServerErrorResponse; public interface OpampClient { + static OpampClientBuilder builder() { + return new OpampClientBuilder(); + } + /** * Starts the client and begin attempts to connect to the Server. Once connection is established * the client will attempt to maintain it by reconnecting if the connection is lost. All failed - * connection attempts will be reported via {@link Callbacks#onConnectFailed(OpampClient, - * Throwable)} callback. + * connection attempts will be reported via {@link Callbacks#onConnectFailed(Throwable)} callback. * *

This method does not wait until the connection to the Server is established and will likely * return before the connection attempts are even made. @@ -40,7 +44,7 @@ public interface OpampClient { * the Server. When called after {@link #start(Callbacks)}, the attributes will be included in the * next outgoing status report. This is typically used by Agents which allow their * AgentDescription to change dynamically while the OpAMPClient is started. May be also called - * from {@link Callbacks#onMessage(OpampClient, MessageData)}. + * from {@link Callbacks#onMessage(MessageData)}. * * @param agentDescription The new agent description. */ @@ -59,20 +63,17 @@ interface Callbacks { * {@link #start(Callbacks)} is called and every time a connection is established to the Server. * For WebSocket clients this is called after the handshake is completed without any error. For * HTTP clients this is called for any request if the response status is OK. - * - * @param client The relevant {@link OpampClient} instance. */ - void onConnect(OpampClient client); + void onConnect(); /** * Called when the connection to the Server cannot be established. May be called after {@link * #start(Callbacks)} is called and tries to connect to the Server. May also be called if the * connection is lost and reconnection attempt fails. * - * @param client The relevant {@link OpampClient} instance. * @param throwable The exception. */ - void onConnectFailed(OpampClient client, Throwable throwable); + void onConnectFailed(@Nullable Throwable throwable); /** * Called when the Server reports an error in response to some previously sent request. Useful @@ -80,10 +81,9 @@ interface Callbacks { * retrying previous operations. The client handles the ErrorResponse_UNAVAILABLE case * internally by performing retries as necessary. * - * @param client The relevant {@link OpampClient} instance. * @param errorResponse The error returned by the Server. */ - void onErrorResponse(OpampClient client, ServerErrorResponse errorResponse); + void onErrorResponse(ServerErrorResponse errorResponse); /** * Called when the Agent receives a message that needs processing. See {@link MessageData} @@ -94,9 +94,8 @@ interface Callbacks { * onMessage returns. This is advisable if processing can take a long time. In that case * returning quickly is preferable to avoid blocking the {@link OpampClient}. * - * @param client The relevant {@link OpampClient} instance. * @param messageData The server response data that needs processing. */ - void onMessage(OpampClient client, MessageData messageData); + void onMessage(MessageData messageData); } } diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java new file mode 100644 index 000000000..7e021c716 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java @@ -0,0 +1,492 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal; + +import com.github.f4b6a3.uuid.UuidCreator; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.opamp.client.internal.connectivity.http.OkHttpSender; +import io.opentelemetry.opamp.client.internal.impl.OpampClientImpl; +import io.opentelemetry.opamp.client.internal.impl.OpampClientState; +import io.opentelemetry.opamp.client.internal.request.service.HttpRequestService; +import io.opentelemetry.opamp.client.internal.request.service.RequestService; +import io.opentelemetry.opamp.client.internal.request.service.WebSocketRequestService; +import io.opentelemetry.opamp.client.internal.state.State; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import javax.annotation.Nullable; +import opamp.proto.AgentCapabilities; +import opamp.proto.AgentDescription; +import opamp.proto.AnyValue; +import opamp.proto.ArrayValue; +import opamp.proto.KeyValue; +import opamp.proto.RemoteConfigStatus; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + * + *

Builds an {@link OpampClient} instance. + */ +public final class OpampClientBuilder { + private final Map identifyingAttributes = new HashMap<>(); + private final Map nonIdentifyingAttributes = new HashMap<>(); + private long capabilities = 0; + private RequestService service = + HttpRequestService.create(OkHttpSender.create("http://localhost:4320/v1/opamp")); + @Nullable private byte[] instanceUid; + @Nullable private State.EffectiveConfig effectiveConfigState; + + OpampClientBuilder() {} + + /** + * Sets an implementation of a {@link RequestService} to handle the request's sending process. + * There are 2 possible options, either {@link HttpRequestService} to use HTTP, or {@link + * WebSocketRequestService} to use WebSocket. + * + * @param service The request service implementation. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder setRequestService(RequestService service) { + this.service = service; + return this; + } + + /** + * Sets the Agent's instance_uid + * value. A random one is generated by default. + * + * @param value The AgentToServer.instance_uid value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder setInstanceUid(byte[] value) { + this.instanceUid = value; + return this; + } + + /** + * Puts a string attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, String value) { + identifyingAttributes.put(key, createStringValue(value)); + return this; + } + + /** + * Puts a boolean attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, boolean value) { + identifyingAttributes.put(key, createBooleanValue(value)); + return this; + } + + /** + * Puts a long (proto int64) attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, long value) { + identifyingAttributes.put(key, createLongValue(value)); + return this; + } + + /** + * Puts a double attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, double value) { + identifyingAttributes.put(key, createDoubleValue(value)); + return this; + } + + /** + * Puts a string array attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, String... values) { + if (values == null) { + return this; + } + identifyingAttributes.put(key, createArrayValue(createStringValueList(values))); + return this; + } + + /** + * Puts a boolean array attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, boolean... values) { + if (values == null) { + return this; + } + identifyingAttributes.put(key, createArrayValue(createBooleanValueList(values))); + return this; + } + + /** + * Puts a long (proto int64) array attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, long... values) { + if (values == null) { + return this; + } + identifyingAttributes.put(key, createArrayValue(createLongValueList(values))); + return this; + } + + /** + * Puts a double array attribute into the identifying_attributes + * field. + * + * @param key The attribute key. + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putIdentifyingAttribute(String key, double... values) { + if (values == null) { + return this; + } + identifyingAttributes.put(key, createArrayValue(createDoubleValueList(values))); + return this; + } + + /** + * Puts a string attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, String value) { + nonIdentifyingAttributes.put(key, createStringValue(value)); + return this; + } + + /** + * Puts a boolean attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, boolean value) { + nonIdentifyingAttributes.put(key, createBooleanValue(value)); + return this; + } + + /** + * Puts a long (proto int64) attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, long value) { + nonIdentifyingAttributes.put(key, createLongValue(value)); + return this; + } + + /** + * Puts a double attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param value The attribute value. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, double value) { + nonIdentifyingAttributes.put(key, createDoubleValue(value)); + return this; + } + + /** + * Puts a string array attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, String... values) { + if (values == null) { + return this; + } + nonIdentifyingAttributes.put(key, createArrayValue(createStringValueList(values))); + return this; + } + + /** + * Puts a boolean array attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, boolean... values) { + if (values == null) { + return this; + } + nonIdentifyingAttributes.put(key, createArrayValue(createBooleanValueList(values))); + return this; + } + + /** + * Puts a long (proto int64) array attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, long... values) { + if (values == null) { + return this; + } + nonIdentifyingAttributes.put(key, createArrayValue(createLongValueList(values))); + return this; + } + + /** + * Puts a double array attribute into the non_identifying_attributes + * field. + * + * @param key The attribute key + * @param values The attribute values. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder putNonIdentifyingAttribute(String key, double... values) { + if (values == null) { + return this; + } + nonIdentifyingAttributes.put(key, createArrayValue(createDoubleValueList(values))); + return this; + } + + /** + * Adds the AcceptsRemoteConfig and ReportsRemoteConfig capabilities to the Client so that the + * Server can offer remote config values as explained here. + * + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder enableRemoteConfig() { + capabilities = + capabilities + | AgentCapabilities.AgentCapabilities_AcceptsRemoteConfig.getValue() + | AgentCapabilities.AgentCapabilities_ReportsRemoteConfig.getValue(); + return this; + } + + /** + * Adds the ReportsEffectiveConfig capability to the Client so that the Server expects the + * Client's effective config report, as explained here. + * + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder enableEffectiveConfigReporting() { + capabilities = + capabilities | AgentCapabilities.AgentCapabilities_ReportsEffectiveConfig.getValue(); + return this; + } + + /** + * Sets the effective config state implementation. It should call {@link + * State.EffectiveConfig#notifyUpdate()} whenever it has changes that have not been sent to the + * server. + * + * @param effectiveConfigState The state implementation. + * @return this + */ + @CanIgnoreReturnValue + public OpampClientBuilder setEffectiveConfigState(State.EffectiveConfig effectiveConfigState) { + this.effectiveConfigState = effectiveConfigState; + return this; + } + + public OpampClient build() { + if (service == null) { + throw new IllegalStateException( + "The request service is not set. You must provide it by calling setRequestService()"); + } + List protoIdentifyingAttributes = new ArrayList<>(); + List protoNonIdentifyingAttributes = new ArrayList<>(); + identifyingAttributes.forEach( + (key, value) -> protoIdentifyingAttributes.add(createKeyValue(key, value))); + nonIdentifyingAttributes.forEach( + (key, value) -> protoNonIdentifyingAttributes.add(createKeyValue(key, value))); + if (instanceUid == null) { + instanceUid = createRandomInstanceUid(); + } + if (effectiveConfigState == null) { + effectiveConfigState = createEffectiveConfigNoop(); + } + OpampClientState state = + new OpampClientState( + new State.RemoteConfigStatus(new RemoteConfigStatus.Builder().build()), + new State.SequenceNum(1L), + new State.AgentDescription( + new AgentDescription.Builder() + .identifying_attributes(protoIdentifyingAttributes) + .non_identifying_attributes(protoNonIdentifyingAttributes) + .build()), + new State.Capabilities(capabilities), + new State.InstanceUid(instanceUid), + new State.Flags(0L), + effectiveConfigState); + return OpampClientImpl.create(service, state); + } + + private static State.EffectiveConfig createEffectiveConfigNoop() { + return new State.EffectiveConfig() { + @Nullable + @Override + public opamp.proto.EffectiveConfig get() { + return null; + } + }; + } + + private static AnyValue createStringValue(String value) { + return new AnyValue.Builder().string_value(value).build(); + } + + private static AnyValue createBooleanValue(boolean value) { + return new AnyValue.Builder().bool_value(value).build(); + } + + private static AnyValue createLongValue(long value) { + return new AnyValue.Builder().int_value(value).build(); + } + + private static AnyValue createDoubleValue(double value) { + return new AnyValue.Builder().double_value(value).build(); + } + + private static List createStringValueList(String[] values) { + List anyValues = new ArrayList<>(); + for (String value : values) { + anyValues.add(createStringValue(value)); + } + return anyValues; + } + + private static List createBooleanValueList(boolean[] values) { + List anyValues = new ArrayList<>(); + for (boolean value : values) { + anyValues.add(createBooleanValue(value)); + } + return anyValues; + } + + private static List createLongValueList(long[] values) { + List anyValues = new ArrayList<>(); + for (long value : values) { + anyValues.add(createLongValue(value)); + } + return anyValues; + } + + private static List createDoubleValueList(double[] values) { + List anyValues = new ArrayList<>(); + for (double value : values) { + anyValues.add(createDoubleValue(value)); + } + return anyValues; + } + + private static AnyValue createArrayValue(List values) { + return new AnyValue.Builder() + .array_value(new ArrayValue.Builder().values(values).build()) + .build(); + } + + private static KeyValue createKeyValue(String key, AnyValue value) { + return new KeyValue.Builder().key(key).value(value).build(); + } + + public static byte[] createRandomInstanceUid() { + UUID uuid = UuidCreator.getTimeOrderedEpoch(); + ByteBuffer buffer = ByteBuffer.allocate(16); + buffer.putLong(uuid.getMostSignificantBits()); + buffer.putLong(uuid.getLeastSignificantBits()); + return buffer.array(); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java index 3e8f9a174..fcfc97ee8 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java @@ -8,14 +8,13 @@ import java.io.IOException; import java.io.InputStream; import java.util.concurrent.CompletableFuture; +import javax.annotation.Nullable; import okhttp3.Call; import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; import okhttp3.RequestBody; import okio.BufferedSink; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; public final class OkHttpSender implements HttpSender { private final OkHttpClient client; @@ -51,17 +50,12 @@ public CompletableFuture send(BodyWriter writer, int contentLength) { .enqueue( new Callback() { @Override - public void onResponse(@NotNull Call call, @NotNull okhttp3.Response response) { - if (response.isSuccessful() && response.body() != null) { - future.complete(new OkHttpResponse(response)); - } else { - future.completeExceptionally( - new HttpErrorException(response.code(), response.message())); - } + public void onResponse(Call call, okhttp3.Response response) { + future.complete(new OkHttpResponse(response)); } @Override - public void onFailure(@NotNull Call call, @NotNull IOException e) { + public void onFailure(Call call, IOException e) { future.completeExceptionally(e); } }); @@ -73,9 +67,6 @@ private static class OkHttpResponse implements Response { private final okhttp3.Response response; private OkHttpResponse(okhttp3.Response response) { - if (response.body() == null) { - throw new IllegalStateException("null response body not expected"); - } this.response = response; } @@ -128,7 +119,7 @@ public long contentLength() { } @Override - public void writeTo(@NotNull BufferedSink bufferedSink) throws IOException { + public void writeTo(BufferedSink bufferedSink) throws IOException { writer.writeTo(bufferedSink.outputStream()); } } diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/package-info.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/package-info.java new file mode 100644 index 000000000..7bbcdb627 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +@ParametersAreNonnullByDefault +package io.opentelemetry.opamp.client.internal.connectivity.http; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java new file mode 100644 index 000000000..8dcf6bb7e --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java @@ -0,0 +1,268 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl; + +import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.internal.impl.recipe.AgentToServerAppenders; +import io.opentelemetry.opamp.client.internal.impl.recipe.RecipeManager; +import io.opentelemetry.opamp.client.internal.impl.recipe.RequestRecipe; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentDescriptionAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentDisconnectAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.CapabilitiesAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.EffectiveConfigAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.FlagsAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.InstanceUidAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.RemoteConfigStatusAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.SequenceNumberAppender; +import io.opentelemetry.opamp.client.internal.request.Field; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.request.service.RequestService; +import io.opentelemetry.opamp.client.internal.response.MessageData; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; +import io.opentelemetry.opamp.client.internal.response.Response; +import io.opentelemetry.opamp.client.internal.state.ObservableState; +import io.opentelemetry.opamp.client.internal.state.State; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import okio.ByteString; +import opamp.proto.AgentDescription; +import opamp.proto.AgentToServer; +import opamp.proto.RemoteConfigStatus; +import opamp.proto.ServerErrorResponse; +import opamp.proto.ServerToAgent; +import opamp.proto.ServerToAgentFlags; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class OpampClientImpl + implements OpampClient, ObservableState.Listener, RequestService.Callback, Supplier { + private final RequestService requestService; + private final AgentToServerAppenders appenders; + private final OpampClientState state; + private final RecipeManager recipeManager; + private final AtomicBoolean isRunning = new AtomicBoolean(false); + private final AtomicBoolean hasStopped = new AtomicBoolean(false); + @Nullable private Callbacks callbacks; + + /** Fields that must always be sent. */ + private static final List REQUIRED_FIELDS; + + /** + * Fields that should only be sent in the first message and then omitted in following messages, + * unless their value changes or the server requests a full message. + * + *

Refer to the + * docs for more details. + */ + private static final List COMPRESSABLE_FIELDS; + + static { + // Required fields init + List constantFields = new ArrayList<>(); + constantFields.add(Field.INSTANCE_UID); + constantFields.add(Field.SEQUENCE_NUM); + constantFields.add(Field.CAPABILITIES); + REQUIRED_FIELDS = Collections.unmodifiableList(constantFields); + + // Compressable fields init + List compressableFields = new ArrayList<>(); + compressableFields.add(Field.AGENT_DESCRIPTION); + compressableFields.add(Field.EFFECTIVE_CONFIG); + compressableFields.add(Field.REMOTE_CONFIG_STATUS); + COMPRESSABLE_FIELDS = Collections.unmodifiableList(compressableFields); + } + + public static OpampClientImpl create(RequestService requestService, OpampClientState state) { + AgentToServerAppenders appenders = + new AgentToServerAppenders( + AgentDescriptionAppender.create(state.agentDescription), + EffectiveConfigAppender.create(state.effectiveConfig), + RemoteConfigStatusAppender.create(state.remoteConfigStatus), + SequenceNumberAppender.create(state.sequenceNum), + CapabilitiesAppender.create(state.capabilities), + InstanceUidAppender.create(state.instanceUid), + FlagsAppender.create(state.flags), + AgentDisconnectAppender.create()); + return new OpampClientImpl( + requestService, appenders, state, RecipeManager.create(REQUIRED_FIELDS)); + } + + private OpampClientImpl( + RequestService requestService, + AgentToServerAppenders appenders, + OpampClientState state, + RecipeManager recipeManager) { + this.requestService = requestService; + this.appenders = appenders; + this.state = state; + this.recipeManager = recipeManager; + } + + @Override + public void start(@Nonnull Callbacks callbacks) { + if (hasStopped.get()) { + throw new IllegalStateException("The client cannot start after it has been stopped."); + } + if (isRunning.compareAndSet(false, true)) { + this.callbacks = callbacks; + requestService.start(this, this); + disableCompression(); + startObservingStateChange(); + requestService.sendRequest(); + } else { + throw new IllegalStateException("The client has already been started"); + } + } + + @Override + public void stop() { + if (isRunning.compareAndSet(true, false)) { + hasStopped.set(true); + stopObservingStateChange(); + prepareDisconnectRequest(); + requestService.stop(); + } + } + + @Override + public void setAgentDescription(@Nonnull AgentDescription agentDescription) { + if (!state.agentDescription.get().equals(agentDescription)) { + state.agentDescription.set(agentDescription); + addFieldAndSend(Field.AGENT_DESCRIPTION); + } + } + + @Override + public void setRemoteConfigStatus(@Nonnull RemoteConfigStatus remoteConfigStatus) { + if (!state.remoteConfigStatus.get().equals(remoteConfigStatus)) { + state.remoteConfigStatus.set(remoteConfigStatus); + addFieldAndSend(Field.REMOTE_CONFIG_STATUS); + } + } + + @Override + public void onConnectionSuccess() { + getCallbacks().onConnect(); + } + + @Override + public void onConnectionFailed(Throwable throwable) { + getCallbacks().onConnectFailed(throwable); + } + + @Override + public void onRequestSuccess(Response response) { + if (response == null) { + return; + } + + handleResponsePayload(response.getServerToAgent()); + } + + @Override + public void onRequestFailed(Throwable throwable) { + preserveFailedRequestRecipe(); + if (throwable instanceof OpampServerResponseException) { + ServerErrorResponse errorResponse = ((OpampServerResponseException) throwable).errorResponse; + getCallbacks().onErrorResponse(errorResponse); + } + } + + private void preserveFailedRequestRecipe() { + RequestRecipe previous = recipeManager.previous(); + if (previous != null) { + recipeManager.next().merge(previous); + } + } + + private void handleResponsePayload(ServerToAgent response) { + int reportFullState = ServerToAgentFlags.ServerToAgentFlags_ReportFullState.getValue(); + if ((response.flags & reportFullState) == reportFullState) { + disableCompression(); + } + handleAgentIdentification(response); + + boolean notifyOnMessage = false; + MessageData.Builder messageBuilder = MessageData.builder(); + + if (response.remote_config != null) { + notifyOnMessage = true; + messageBuilder.setRemoteConfig(response.remote_config); + } + + if (notifyOnMessage) { + getCallbacks().onMessage(messageBuilder.build()); + } + } + + private void handleAgentIdentification(ServerToAgent response) { + if (response.agent_identification != null) { + ByteString newInstanceUid = response.agent_identification.new_instance_uid; + if (newInstanceUid.size() > 0) { + state.instanceUid.set(newInstanceUid.toByteArray()); + } + } + } + + private void disableCompression() { + recipeManager.next().addAllFields(COMPRESSABLE_FIELDS); + } + + private void prepareDisconnectRequest() { + recipeManager.next().addField(Field.AGENT_DISCONNECT); + } + + @Nonnull + private Callbacks getCallbacks() { + return Objects.requireNonNull(callbacks); + } + + @Override + public Request get() { + AgentToServer.Builder builder = new AgentToServer.Builder(); + for (Field field : recipeManager.next().build().getFields()) { + appenders.getForField(field).appendTo(builder); + } + Request request = Request.create(builder.build()); + state.sequenceNum.increment(); + return request; + } + + private void startObservingStateChange() { + for (State state : state.getAll()) { + if (state instanceof ObservableState) { + ((ObservableState) state).addListener(this); + } + } + } + + private void stopObservingStateChange() { + for (State state : state.getAll()) { + if (state instanceof ObservableState) { + ((ObservableState) state).removeListener(this); + } + } + } + + @Override + public void onStateUpdate(Field type) { + addFieldAndSend(type); + } + + private void addFieldAndSend(Field field) { + recipeManager.next().addField(field); + requestService.sendRequest(); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientState.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientState.java new file mode 100644 index 000000000..d8b26beee --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientState.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl; + +import io.opentelemetry.opamp.client.internal.state.State; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class OpampClientState { + public final State.RemoteConfigStatus remoteConfigStatus; + public final State.SequenceNum sequenceNum; + public final State.AgentDescription agentDescription; + public final State.Capabilities capabilities; + public final State.InstanceUid instanceUid; + public final State.Flags flags; + public final State.EffectiveConfig effectiveConfig; + private final List> items; + + public OpampClientState( + State.RemoteConfigStatus remoteConfigStatus, + State.SequenceNum sequenceNum, + State.AgentDescription agentDescription, + State.Capabilities capabilities, + State.InstanceUid instanceUid, + State.Flags flags, + State.EffectiveConfig effectiveConfig) { + this.remoteConfigStatus = remoteConfigStatus; + this.sequenceNum = sequenceNum; + this.agentDescription = agentDescription; + this.capabilities = capabilities; + this.instanceUid = instanceUid; + this.flags = flags; + this.effectiveConfig = effectiveConfig; + + List> providedItems = new ArrayList<>(); + providedItems.add(remoteConfigStatus); + providedItems.add(sequenceNum); + providedItems.add(agentDescription); + providedItems.add(capabilities); + providedItems.add(instanceUid); + providedItems.add(flags); + providedItems.add(effectiveConfig); + + items = Collections.unmodifiableList(providedItems); + } + + public List> getAll() { + return items; + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppenders.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppenders.java new file mode 100644 index 000000000..bbde2def9 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppenders.java @@ -0,0 +1,73 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe; + +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentDescriptionAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentDisconnectAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentToServerAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.CapabilitiesAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.EffectiveConfigAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.FlagsAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.InstanceUidAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.RemoteConfigStatusAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.SequenceNumberAppender; +import io.opentelemetry.opamp.client.internal.request.Field; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AgentToServerAppenders { + public final AgentDescriptionAppender agentDescriptionAppender; + public final EffectiveConfigAppender effectiveConfigAppender; + public final RemoteConfigStatusAppender remoteConfigStatusAppender; + public final SequenceNumberAppender sequenceNumberAppender; + public final CapabilitiesAppender capabilitiesAppender; + public final InstanceUidAppender instanceUidAppender; + public final FlagsAppender flagsAppender; + public final AgentDisconnectAppender agentDisconnectAppender; + private final Map allAppenders; + + public AgentToServerAppenders( + AgentDescriptionAppender agentDescriptionAppender, + EffectiveConfigAppender effectiveConfigAppender, + RemoteConfigStatusAppender remoteConfigStatusAppender, + SequenceNumberAppender sequenceNumberAppender, + CapabilitiesAppender capabilitiesAppender, + InstanceUidAppender instanceUidAppender, + FlagsAppender flagsAppender, + AgentDisconnectAppender agentDisconnectAppender) { + this.agentDescriptionAppender = agentDescriptionAppender; + this.effectiveConfigAppender = effectiveConfigAppender; + this.remoteConfigStatusAppender = remoteConfigStatusAppender; + this.sequenceNumberAppender = sequenceNumberAppender; + this.capabilitiesAppender = capabilitiesAppender; + this.instanceUidAppender = instanceUidAppender; + this.flagsAppender = flagsAppender; + this.agentDisconnectAppender = agentDisconnectAppender; + + Map appenders = new HashMap<>(); + appenders.put(Field.AGENT_DESCRIPTION, agentDescriptionAppender); + appenders.put(Field.EFFECTIVE_CONFIG, effectiveConfigAppender); + appenders.put(Field.REMOTE_CONFIG_STATUS, remoteConfigStatusAppender); + appenders.put(Field.SEQUENCE_NUM, sequenceNumberAppender); + appenders.put(Field.CAPABILITIES, capabilitiesAppender); + appenders.put(Field.INSTANCE_UID, instanceUidAppender); + appenders.put(Field.FLAGS, flagsAppender); + appenders.put(Field.AGENT_DISCONNECT, agentDisconnectAppender); + allAppenders = Collections.unmodifiableMap(appenders); + } + + public AgentToServerAppender getForField(Field type) { + if (!allAppenders.containsKey(type)) { + throw new IllegalArgumentException("Field type " + type + " is not supported"); + } + return allAppenders.get(type); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManager.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManager.java new file mode 100644 index 000000000..86a72f72b --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManager.java @@ -0,0 +1,86 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.opamp.client.internal.request.Field; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class RecipeManager { + private final Object recipeLock = new Object(); + private final List constantFields; + @Nullable private RequestRecipe previousRecipe = null; + @Nullable private RecipeBuilder builder; + + public static RecipeManager create(List constantFields) { + return new RecipeManager(Collections.unmodifiableList(constantFields)); + } + + private RecipeManager(List constantFields) { + this.constantFields = constantFields; + } + + @Nullable + public RequestRecipe previous() { + synchronized (recipeLock) { + return previousRecipe; + } + } + + @Nonnull + public RecipeBuilder next() { + synchronized (recipeLock) { + if (builder == null) { + builder = new RecipeBuilder(constantFields); + } + return builder; + } + } + + public final class RecipeBuilder { + private final Set fields = new HashSet<>(); + + @CanIgnoreReturnValue + public RecipeBuilder addField(Field field) { + fields.add(field); + return this; + } + + @CanIgnoreReturnValue + public RecipeBuilder addAllFields(Collection fields) { + this.fields.addAll(fields); + return this; + } + + @CanIgnoreReturnValue + public RecipeBuilder merge(RequestRecipe recipe) { + return addAllFields(recipe.getFields()); + } + + public RequestRecipe build() { + synchronized (recipeLock) { + RequestRecipe recipe = new RequestRecipe(Collections.unmodifiableCollection(fields)); + previousRecipe = recipe; + builder = null; + return recipe; + } + } + + private RecipeBuilder(List initialFields) { + fields.addAll(initialFields); + } + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RequestRecipe.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RequestRecipe.java new file mode 100644 index 000000000..4887cb1f2 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/RequestRecipe.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe; + +import io.opentelemetry.opamp.client.internal.request.Field; +import java.util.Collection; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class RequestRecipe { + private final Collection fields; + + public RequestRecipe(Collection fields) { + this.fields = fields; + } + + public Collection getFields() { + return fields; + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDescriptionAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDescriptionAppender.java new file mode 100644 index 000000000..260ebef02 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDescriptionAppender.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import opamp.proto.AgentDescription; +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AgentDescriptionAppender implements AgentToServerAppender { + private final Supplier data; + + public static AgentDescriptionAppender create(Supplier data) { + return new AgentDescriptionAppender(data); + } + + private AgentDescriptionAppender(Supplier data) { + this.data = data; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.agent_description(data.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDisconnectAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDisconnectAppender.java new file mode 100644 index 000000000..7259f005c --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentDisconnectAppender.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import opamp.proto.AgentDisconnect; +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class AgentDisconnectAppender implements AgentToServerAppender { + + public static AgentDisconnectAppender create() { + return new AgentDisconnectAppender(); + } + + private AgentDisconnectAppender() {} + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.agent_disconnect(new AgentDisconnect.Builder().build()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentToServerAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentToServerAppender.java new file mode 100644 index 000000000..b2ffc9312 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/AgentToServerAppender.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + * + *

AgentToServer request builder appender. Each implementation should match one of the + * AgentToServer fields and ensure the field is added to a request. + */ +public interface AgentToServerAppender { + /** + * Appends its data to the builder. + * + * @param builder The AgentToServer message builder. + */ + void appendTo(AgentToServer.Builder builder); +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/CapabilitiesAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/CapabilitiesAppender.java new file mode 100644 index 000000000..ba55ba431 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/CapabilitiesAppender.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class CapabilitiesAppender implements AgentToServerAppender { + private final Supplier capabilities; + + public static CapabilitiesAppender create(Supplier capabilities) { + return new CapabilitiesAppender(capabilities); + } + + private CapabilitiesAppender(Supplier capabilities) { + this.capabilities = capabilities; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.capabilities(capabilities.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/EffectiveConfigAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/EffectiveConfigAppender.java new file mode 100644 index 000000000..2816a7ec1 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/EffectiveConfigAppender.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import opamp.proto.AgentToServer; +import opamp.proto.EffectiveConfig; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class EffectiveConfigAppender implements AgentToServerAppender { + private final Supplier effectiveConfig; + + public static EffectiveConfigAppender create(Supplier effectiveConfig) { + return new EffectiveConfigAppender(effectiveConfig); + } + + private EffectiveConfigAppender(Supplier effectiveConfig) { + this.effectiveConfig = effectiveConfig; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.effective_config(effectiveConfig.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/FlagsAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/FlagsAppender.java new file mode 100644 index 000000000..0fea9db36 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/FlagsAppender.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class FlagsAppender implements AgentToServerAppender { + private final Supplier flags; + + public static FlagsAppender create(Supplier flags) { + return new FlagsAppender(flags); + } + + private FlagsAppender(Supplier flags) { + this.flags = flags; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.flags(flags.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/InstanceUidAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/InstanceUidAppender.java new file mode 100644 index 000000000..2ee0213fe --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/InstanceUidAppender.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import okio.ByteString; +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class InstanceUidAppender implements AgentToServerAppender { + private final Supplier instanceUid; + + public static InstanceUidAppender create(Supplier instanceUid) { + return new InstanceUidAppender(instanceUid); + } + + private InstanceUidAppender(Supplier instanceUid) { + this.instanceUid = instanceUid; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.instance_uid(ByteString.of(instanceUid.get())); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/RemoteConfigStatusAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/RemoteConfigStatusAppender.java new file mode 100644 index 000000000..63b411a65 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/RemoteConfigStatusAppender.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import opamp.proto.AgentToServer; +import opamp.proto.RemoteConfigStatus; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class RemoteConfigStatusAppender implements AgentToServerAppender { + private final Supplier remoteConfigStatus; + + public static RemoteConfigStatusAppender create(Supplier remoteConfigStatus) { + return new RemoteConfigStatusAppender(remoteConfigStatus); + } + + private RemoteConfigStatusAppender(Supplier remoteConfigStatus) { + this.remoteConfigStatus = remoteConfigStatus; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.remote_config_status(remoteConfigStatus.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/SequenceNumberAppender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/SequenceNumberAppender.java new file mode 100644 index 000000000..d73bcd47b --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/recipe/appenders/SequenceNumberAppender.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe.appenders; + +import java.util.function.Supplier; +import opamp.proto.AgentToServer; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public final class SequenceNumberAppender implements AgentToServerAppender { + private final Supplier sequenceNumber; + + public static SequenceNumberAppender create(Supplier sequenceNumber) { + return new SequenceNumberAppender(sequenceNumber); + } + + private SequenceNumberAppender(Supplier sequenceNumber) { + this.sequenceNumber = sequenceNumber; + } + + @Override + public void appendTo(AgentToServer.Builder builder) { + builder.sequence_num(sequenceNumber.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/package-info.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/package-info.java new file mode 100644 index 000000000..b972b795a --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +@ParametersAreNonnullByDefault +package io.opentelemetry.opamp.client.internal; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Field.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Field.java new file mode 100644 index 000000000..277ecc2dd --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/Field.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + * + *

List of supported AgentToServer + * message fields. + */ +public enum Field { + INSTANCE_UID, + SEQUENCE_NUM, + AGENT_DESCRIPTION, + CAPABILITIES, + EFFECTIVE_CONFIG, + REMOTE_CONFIG_STATUS, + AGENT_DISCONNECT, + FLAGS +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java index 1b196218b..da1759c22 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java @@ -11,7 +11,7 @@ import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; -import io.opentelemetry.opamp.client.internal.response.OpampServerResponseError; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; import java.io.IOException; import java.time.Duration; @@ -206,7 +206,9 @@ private void handleErrorResponse(ServerErrorResponse errorResponse) { } connectionStatus.retryAfter(retryAfter); } - getCallback().onRequestFailed(new OpampServerResponseError(errorResponse.error_message)); + getCallback() + .onRequestFailed( + new OpampServerResponseException(errorResponse, errorResponse.error_message)); } private Callback getCallback() { diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java index a55a1be70..2bd283631 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java @@ -10,7 +10,7 @@ import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; -import io.opentelemetry.opamp.client.internal.response.OpampServerResponseError; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -168,11 +168,12 @@ public void onMessage(byte[] data) { try { ServerToAgent serverToAgent = readServerToAgent(data); - if (serverToAgent.error_response != null) { - handleServerError(serverToAgent.error_response); + ServerErrorResponse errorResponse = serverToAgent.error_response; + if (errorResponse != null) { + handleServerError(errorResponse); getCallback() .onRequestFailed( - new OpampServerResponseError(serverToAgent.error_response.error_message)); + new OpampServerResponseException(errorResponse, errorResponse.error_message)); return; } diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java deleted file mode 100644 index 08ad6a4d2..000000000 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseError.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.opamp.client.internal.response; - -public class OpampServerResponseError extends Exception { - private static final long serialVersionUID = 1L; - - /** - * Constructs an OpAMP error related exception. - * - * @param message The OpAMP error message. - */ - public OpampServerResponseError(String message) { - super(message); - } -} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseException.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseException.java new file mode 100644 index 000000000..5eb3fba57 --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/OpampServerResponseException.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.response; + +import opamp.proto.ServerErrorResponse; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +public class OpampServerResponseException extends Exception { + private static final long serialVersionUID = 1L; + + public final ServerErrorResponse errorResponse; + + /** + * Constructs an OpAMP error related exception. + * + * @param errorResponse The OpAMP error. + * @param message The OpAMP error message. + */ + public OpampServerResponseException(ServerErrorResponse errorResponse, String message) { + super(message); + this.errorResponse = errorResponse; + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java new file mode 100644 index 000000000..89ba8b1cb --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.state; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; +import javax.annotation.Nonnull; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + */ +abstract class InMemoryState implements State { + private final AtomicReference state = new AtomicReference<>(); + + public InMemoryState(T initialValue) { + if (initialValue == null) { + throw new IllegalArgumentException("The value must not be null"); + } + state.set(initialValue); + } + + public void set(T value) { + if (value == null) { + throw new IllegalArgumentException("The value must not be null"); + } + state.set(value); + } + + @Nonnull + @Override + public T get() { + return Objects.requireNonNull(state.get()); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/ObservableState.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/ObservableState.java new file mode 100644 index 000000000..bfd4d46bc --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/ObservableState.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.state; + +import io.opentelemetry.opamp.client.internal.request.Field; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + * + *

Implementations of state that cannot be stored in memory. Users would need to implement its + * {@link #get()} method and ensure to call {@link #notifyUpdate()} whenever new data is available + * for the next client request. + */ +public abstract class ObservableState implements State { + private final Set listeners = Collections.synchronizedSet(new HashSet<>()); + + public final void addListener(Listener listener) { + listeners.add(listener); + } + + public final void removeListener(Listener listener) { + listeners.remove(listener); + } + + public final void notifyUpdate() { + synchronized (listeners) { + for (Listener listener : listeners) { + listener.onStateUpdate(getFieldType()); + } + } + } + + public interface Listener { + /** + * Notifies that there's new data available for this state, so that the client includes it in + * the next request. + * + * @param type The field type associated to this state. + */ + void onStateUpdate(Field type); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/State.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/State.java new file mode 100644 index 000000000..abb0a6a9d --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/State.java @@ -0,0 +1,108 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.state; + +import io.opentelemetry.opamp.client.internal.request.Field; +import java.util.Objects; +import java.util.function.Supplier; +import javax.annotation.Nonnull; + +/** + * This class is internal and is hence not for public use. Its APIs are unstable and can change at + * any time. + * + *

Abstraction for a client request field that carries data. Each implementation can only be + * linked to one type of client request field, which is provided in its {@link #getFieldType()} + * method. + */ +public interface State extends Supplier { + + Field getFieldType(); + + @Nonnull + default T mustGet() { + return Objects.requireNonNull(get()); + } + + final class InstanceUid extends InMemoryState { + public InstanceUid(byte[] initialValue) { + super(initialValue); + } + + @Override + public Field getFieldType() { + return Field.INSTANCE_UID; + } + } + + final class SequenceNum extends InMemoryState { + public SequenceNum(Long initialValue) { + super(initialValue); + } + + public void increment() { + set(mustGet() + 1); + } + + @Override + public Field getFieldType() { + return Field.SEQUENCE_NUM; + } + } + + final class AgentDescription extends InMemoryState { + public AgentDescription(opamp.proto.AgentDescription initialValue) { + super(initialValue); + } + + @Override + public Field getFieldType() { + return Field.AGENT_DESCRIPTION; + } + } + + final class Capabilities extends InMemoryState { + public Capabilities(Long initialValue) { + super(initialValue); + } + + @Override + public Field getFieldType() { + return Field.CAPABILITIES; + } + } + + final class RemoteConfigStatus extends InMemoryState { + + public RemoteConfigStatus(opamp.proto.RemoteConfigStatus initialValue) { + super(initialValue); + } + + @Override + public Field getFieldType() { + return Field.REMOTE_CONFIG_STATUS; + } + } + + final class Flags extends InMemoryState { + + public Flags(Long initialValue) { + super(initialValue); + } + + @Override + public Field getFieldType() { + return Field.FLAGS; + } + } + + abstract class EffectiveConfig extends ObservableState { + @Override + public final Field getFieldType() { + return Field.EFFECTIVE_CONFIG; + } + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java new file mode 100644 index 000000000..ec630dc7e --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java @@ -0,0 +1,485 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.awaitility.Awaitility.await; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.internal.connectivity.http.OkHttpSender; +import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.request.service.HttpRequestService; +import io.opentelemetry.opamp.client.internal.request.service.RequestService; +import io.opentelemetry.opamp.client.internal.response.MessageData; +import io.opentelemetry.opamp.client.internal.state.State; +import java.io.IOException; +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Supplier; +import javax.annotation.Nonnull; +import mockwebserver3.MockResponse; +import mockwebserver3.MockWebServer; +import mockwebserver3.RecordedRequest; +import mockwebserver3.junit5.StartStop; +import okio.Buffer; +import okio.ByteString; +import opamp.proto.AgentConfigFile; +import opamp.proto.AgentConfigMap; +import opamp.proto.AgentDescription; +import opamp.proto.AgentIdentification; +import opamp.proto.AgentRemoteConfig; +import opamp.proto.AgentToServer; +import opamp.proto.AgentToServerFlags; +import opamp.proto.AnyValue; +import opamp.proto.EffectiveConfig; +import opamp.proto.KeyValue; +import opamp.proto.RemoteConfigStatus; +import opamp.proto.RemoteConfigStatuses; +import opamp.proto.ServerErrorResponse; +import opamp.proto.ServerToAgent; +import opamp.proto.ServerToAgentFlags; +import org.jetbrains.annotations.Nullable; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class OpampClientImplTest { + private RequestService requestService; + private OpampClientState state; + private OpampClientImpl client; + private TestEffectiveConfig effectiveConfig; + private TestCallbacks callbacks; + @StartStop private final MockWebServer server = new MockWebServer(); + + @BeforeEach + void setUp() { + effectiveConfig = + new TestEffectiveConfig( + new EffectiveConfig.Builder() + .config_map(createAgentConfigMap("first", "first content")) + .build()); + state = + new OpampClientState( + new State.RemoteConfigStatus( + getRemoteConfigStatus(RemoteConfigStatuses.RemoteConfigStatuses_UNSET)), + new State.SequenceNum(1L), + new State.AgentDescription(new AgentDescription.Builder().build()), + new State.Capabilities(5L), + new State.InstanceUid(new byte[] {1, 2, 3}), + new State.Flags((long) AgentToServerFlags.AgentToServerFlags_Unspecified.getValue()), + effectiveConfig); + requestService = createHttpService(); + } + + @AfterEach + void tearDown() { + client.stop(); + } + + @Test + void verifyFieldsSent() { + // Check first request + ServerToAgent response = new ServerToAgent.Builder().build(); + RecordedRequest firstRequest = initializeClient(response); + AgentToServer firstMessage = getAgentToServerMessage(firstRequest); + + // Required first request fields + assertThat(firstMessage.instance_uid).isNotNull(); + assertThat(firstMessage.sequence_num).isEqualTo(1); + assertThat(firstMessage.capabilities).isEqualTo(state.capabilities.get()); + assertThat(firstMessage.agent_description).isEqualTo(state.agentDescription.get()); + assertThat(firstMessage.effective_config).isEqualTo(state.effectiveConfig.get()); + assertThat(firstMessage.remote_config_status).isEqualTo(state.remoteConfigStatus.get()); + + // Check second request + enqueueServerToAgentResponse(response); + RemoteConfigStatus remoteConfigStatus = + new RemoteConfigStatus.Builder() + .status(RemoteConfigStatuses.RemoteConfigStatuses_APPLYING) + .build(); + client.setRemoteConfigStatus(remoteConfigStatus); + + RecordedRequest secondRequest = takeRequest(); + AgentToServer secondMessage = getAgentToServerMessage(secondRequest); + + // Verify only changed and required fields are present + assertThat(secondMessage.instance_uid).isNotNull(); + assertThat(secondMessage.sequence_num).isEqualTo(2); + assertThat(firstMessage.capabilities).isEqualTo(state.capabilities.get()); + assertThat(secondMessage.agent_description).isNull(); + assertThat(secondMessage.effective_config).isNull(); + assertThat(secondMessage.remote_config_status).isEqualTo(remoteConfigStatus); + + // Check state observing + enqueueServerToAgentResponse(response); + EffectiveConfig otherConfig = + new EffectiveConfig.Builder() + .config_map(createAgentConfigMap("other", "other value")) + .build(); + effectiveConfig.config = otherConfig; + effectiveConfig.notifyUpdate(); + + // Check third request + RecordedRequest thirdRequest = takeRequest(); + AgentToServer thirdMessage = getAgentToServerMessage(thirdRequest); + + assertThat(thirdMessage.instance_uid).isNotNull(); + assertThat(thirdMessage.sequence_num).isEqualTo(3); + assertThat(firstMessage.capabilities).isEqualTo(state.capabilities.get()); + assertThat(thirdMessage.agent_description).isNull(); + assertThat(thirdMessage.remote_config_status).isNull(); + assertThat(thirdMessage.effective_config) + .isEqualTo(otherConfig); // it was changed via observable state + + // Check when the server requests for all fields + + ServerToAgent reportFullState = + new ServerToAgent.Builder() + .flags(ServerToAgentFlags.ServerToAgentFlags_ReportFullState.getValue()) + .build(); + enqueueServerToAgentResponse(reportFullState); + requestService.sendRequest(); + takeRequest(); // Notifying the client to send all fields next time + + // Request with all fields + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + requestService.sendRequest(); + + AgentToServer fullRequestedMessage = getAgentToServerMessage(takeRequest()); + + // Required first request fields + assertThat(fullRequestedMessage.instance_uid).isNotNull(); + assertThat(fullRequestedMessage.sequence_num).isEqualTo(5); + assertThat(fullRequestedMessage.capabilities).isEqualTo(state.capabilities.get()); + assertThat(fullRequestedMessage.agent_description).isEqualTo(state.agentDescription.get()); + assertThat(fullRequestedMessage.effective_config).isEqualTo(state.effectiveConfig.get()); + assertThat(fullRequestedMessage.remote_config_status).isEqualTo(state.remoteConfigStatus.get()); + } + + @Test + void verifyStop() { + initializeClient(); + + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + client.stop(); + + AgentToServer agentToServerMessage = getAgentToServerMessage(takeRequest()); + assertThat(agentToServerMessage.agent_disconnect).isNotNull(); + } + + @Test + void verifyStartOnlyOnce() { + initializeClient(); + try { + client.start(callbacks); + fail("Should have thrown an exception"); + } catch (IllegalStateException e) { + assertThat(e).hasMessage("The client has already been started"); + } + } + + @Test + void onSuccess_withChangesToReport_notifyCallbackOnMessage() { + initializeClient(); + AgentRemoteConfig remoteConfig = + new AgentRemoteConfig.Builder() + .config(createAgentConfigMap("someKey", "someValue")) + .build(); + ServerToAgent serverToAgent = new ServerToAgent.Builder().remote_config(remoteConfig).build(); + enqueueServerToAgentResponse(serverToAgent); + + // Force request + requestService.sendRequest(); + + // Await for onMessage call + await().atMost(Duration.ofSeconds(1)).until(() -> callbacks.onMessageCalls.get() == 1); + + verify(callbacks).onMessage(MessageData.builder().setRemoteConfig(remoteConfig).build()); + } + + @Test + void onSuccess_withNoChangesToReport_doNotNotifyCallbackOnMessage() { + initializeClient(); + ServerToAgent serverToAgent = new ServerToAgent.Builder().build(); + enqueueServerToAgentResponse(serverToAgent); + + // Force request + requestService.sendRequest(); + + // Giving some time for the callback to get called + await().during(Duration.ofSeconds(1)); + + verify(callbacks, never()).onMessage(any()); + } + + @Test + void verifyAgentDescriptionSetter() { + initializeClient(); + AgentDescription agentDescription = + getAgentDescriptionWithOneIdentifyingValue("service.name", "My service"); + + // Update when changed + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + client.setAgentDescription(agentDescription); + assertThat(takeRequest()).isNotNull(); + + // Ignore when the provided value is the same as the current one + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + client.setAgentDescription(agentDescription); + assertThat(takeRequest()).isNull(); + } + + @Test + void verifyRemoteConfigStatusSetter() { + initializeClient(); + RemoteConfigStatus remoteConfigStatus = + getRemoteConfigStatus(RemoteConfigStatuses.RemoteConfigStatuses_APPLYING); + + // Update when changed + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + client.setRemoteConfigStatus(remoteConfigStatus); + assertThat(takeRequest()).isNotNull(); + + // Ignore when the provided value is the same as the current one + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + client.setRemoteConfigStatus(remoteConfigStatus); + assertThat(takeRequest()).isNull(); + } + + @Test + void onConnectionSuccessful_notifyCallback() { + initializeClient(); + + await().atMost(Duration.ofSeconds(1)).until(() -> callbacks.onConnectCalls.get() == 1); + + verify(callbacks).onConnect(); + verify(callbacks, never()).onConnectFailed(any()); + } + + @Test + void onFailedResponse_keepFieldsForNextRequest() { + initializeClient(); + + // Mock failed request + server.enqueue(new MockResponse.Builder().code(404).build()); + + // Adding a non-constant field + AgentDescription agentDescription = + getAgentDescriptionWithOneIdentifyingValue("service.namespace", "something"); + client.setAgentDescription(agentDescription); + + // Assert first request contains it + assertThat(getAgentToServerMessage(takeRequest()).agent_description) + .isEqualTo(agentDescription); + + // Since it failed, send the agent description field in the next request + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + requestService.sendRequest(); + assertThat(getAgentToServerMessage(takeRequest()).agent_description) + .isEqualTo(agentDescription); + + // When there's no failure, do not keep it. + enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); + requestService.sendRequest(); + assertThat(getAgentToServerMessage(takeRequest()).agent_description).isNull(); + } + + @Test + void onFailedResponse_withServerErrorData_notifyCallback() { + initializeClient(); + + ServerErrorResponse errorResponse = new ServerErrorResponse.Builder().build(); + enqueueServerToAgentResponse(new ServerToAgent.Builder().error_response(errorResponse).build()); + + // Force request + requestService.sendRequest(); + + await().atMost(Duration.ofSeconds(1)).until(() -> callbacks.onErrorResponseCalls.get() == 1); + + verify(callbacks).onErrorResponse(errorResponse); + verify(callbacks, never()).onMessage(any()); + } + + @Test + void onConnectionFailed_notifyCallback() { + initializeClient(); + Throwable throwable = new Throwable(); + + client.onConnectionFailed(throwable); + + verify(callbacks).onConnectFailed(throwable); + } + + @Test + void whenServerProvidesNewInstanceUid_useIt() { + initializeClient(); + byte[] initialUid = state.instanceUid.get(); + + byte[] serverProvidedUid = new byte[] {1, 2, 3}; + ServerToAgent response = + new ServerToAgent.Builder() + .agent_identification( + new AgentIdentification.Builder() + .new_instance_uid(ByteString.of(serverProvidedUid)) + .build()) + .build(); + + enqueueServerToAgentResponse(response); + requestService.sendRequest(); + + await().atMost(Duration.ofSeconds(1)).until(() -> state.instanceUid.get() != initialUid); + + assertThat(state.instanceUid.get()).isEqualTo(serverProvidedUid); + } + + private static AgentToServer getAgentToServerMessage(RecordedRequest request) { + try { + return AgentToServer.ADAPTER.decode(Objects.requireNonNull(request.getBody())); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private RecordedRequest takeRequest() { + try { + return server.takeRequest(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + private void enqueueServerToAgentResponse(ServerToAgent response) { + server.enqueue(getMockResponse(response)); + } + + @Nonnull + private static MockResponse getMockResponse(ServerToAgent response) { + Buffer bodyBuffer = new Buffer(); + bodyBuffer.write(response.encode()); + return new MockResponse.Builder().code(200).body(bodyBuffer).build(); + } + + private static RemoteConfigStatus getRemoteConfigStatus(RemoteConfigStatuses status) { + return new RemoteConfigStatus.Builder().status(status).build(); + } + + private static AgentConfigMap createAgentConfigMap(String key, String content) { + Map keyToFile = new HashMap<>(); + keyToFile.put(key, new AgentConfigFile.Builder().body(ByteString.encodeUtf8(content)).build()); + return new AgentConfigMap.Builder().config_map(keyToFile).build(); + } + + private static AgentDescription getAgentDescriptionWithOneIdentifyingValue( + String key, String value) { + KeyValue keyValue = + new KeyValue.Builder() + .key(key) + .value(new AnyValue.Builder().string_value(value).build()) + .build(); + List keyValues = new ArrayList<>(); + keyValues.add(keyValue); + return new AgentDescription.Builder().identifying_attributes(keyValues).build(); + } + + private RecordedRequest initializeClient() { + return initializeClient(new ServerToAgent.Builder().build()); + } + + private RecordedRequest initializeClient(ServerToAgent initialResponse) { + client = OpampClientImpl.create(requestService, state); + + // Prepare first request on start + enqueueServerToAgentResponse(initialResponse); + + callbacks = spy(new TestCallbacks()); + client.start(callbacks); + return takeRequest(); + } + + private static class TestEffectiveConfig extends State.EffectiveConfig { + private opamp.proto.EffectiveConfig config; + + public TestEffectiveConfig(opamp.proto.EffectiveConfig initialValue) { + config = initialValue; + } + + @Override + public opamp.proto.EffectiveConfig get() { + return config; + } + } + + private RequestService createHttpService() { + return new TestHttpRequestService( + HttpRequestService.create(OkHttpSender.create(server.url("/v1/opamp").toString()))); + } + + private static class TestHttpRequestService implements RequestService { + private final HttpRequestService delegate; + + private TestHttpRequestService(HttpRequestService delegate) { + this.delegate = delegate; + } + + @Override + public void start(Callback callback, Supplier requestSupplier) { + delegate.start(callback, requestSupplier); + } + + @Override + public void sendRequest() { + delegate.sendRequest(); + } + + @Override + public void stop() { + // This is to verify agent disconnect field presence for the websocket use case. + delegate.sendRequest(); + delegate.stop(); + } + } + + private static class TestCallbacks implements OpampClient.Callbacks { + private final AtomicInteger onConnectCalls = new AtomicInteger(); + private final AtomicInteger onConnectFailedCalls = new AtomicInteger(); + private final AtomicInteger onErrorResponseCalls = new AtomicInteger(); + private final AtomicInteger onMessageCalls = new AtomicInteger(); + + @Override + public void onConnect() { + onConnectCalls.incrementAndGet(); + } + + @Override + public void onConnectFailed(@Nullable Throwable throwable) { + onConnectFailedCalls.incrementAndGet(); + } + + @Override + public void onErrorResponse(ServerErrorResponse errorResponse) { + onErrorResponseCalls.incrementAndGet(); + } + + @Override + public void onMessage(MessageData messageData) { + onMessageCalls.incrementAndGet(); + } + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientStateTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientStateTest.java new file mode 100644 index 000000000..000b4a6fe --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientStateTest.java @@ -0,0 +1,43 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.opamp.client.internal.state.State; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +@SuppressWarnings("UnusedVariable") +class OpampClientStateTest { + @Mock private State.RemoteConfigStatus remoteConfigStatus; + @Mock private State.SequenceNum sequenceNum; + @Mock private State.AgentDescription agentDescription; + @Mock private State.Capabilities capabilities; + @Mock private State.InstanceUid instanceUid; + @Mock private State.Flags flags; + @Mock private State.EffectiveConfig effectiveConfig; + @InjectMocks private OpampClientState state; + + @Test + void verifyAllFields() throws IllegalAccessException { + List> stateFields = new ArrayList<>(); + for (Field field : OpampClientState.class.getFields()) { + if (State.class.isAssignableFrom(field.getType())) { + stateFields.add((State) field.get(state)); + } + } + + assertThat(state.getAll()).containsAll(stateFields); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppendersTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppendersTest.java new file mode 100644 index 000000000..6bab5615f --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/AgentToServerAppendersTest.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentDescriptionAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentDisconnectAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.AgentToServerAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.CapabilitiesAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.EffectiveConfigAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.FlagsAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.InstanceUidAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.RemoteConfigStatusAppender; +import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.SequenceNumberAppender; +import io.opentelemetry.opamp.client.internal.request.Field; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class AgentToServerAppendersTest { + @Mock private AgentDescriptionAppender agentDescriptionAppender; + @Mock private EffectiveConfigAppender effectiveConfigAppender; + @Mock private RemoteConfigStatusAppender remoteConfigStatusAppender; + @Mock private SequenceNumberAppender sequenceNumberAppender; + @Mock private CapabilitiesAppender capabilitiesAppender; + @Mock private FlagsAppender flagsAppender; + @Mock private InstanceUidAppender instanceUidAppender; + @Mock private AgentDisconnectAppender agentDisconnectAppender; + @InjectMocks private AgentToServerAppenders appenders; + + @Test + void verifyAppenderList() { + verifyMapping(Field.AGENT_DESCRIPTION, agentDescriptionAppender); + verifyMapping(Field.EFFECTIVE_CONFIG, effectiveConfigAppender); + verifyMapping(Field.REMOTE_CONFIG_STATUS, remoteConfigStatusAppender); + verifyMapping(Field.SEQUENCE_NUM, sequenceNumberAppender); + verifyMapping(Field.CAPABILITIES, capabilitiesAppender); + verifyMapping(Field.INSTANCE_UID, instanceUidAppender); + verifyMapping(Field.FLAGS, flagsAppender); + verifyMapping(Field.AGENT_DISCONNECT, agentDisconnectAppender); + } + + private void verifyMapping(Field type, AgentToServerAppender appender) { + assertThat(appenders.getForField(type)).isEqualTo(appender); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManagerTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManagerTest.java new file mode 100644 index 000000000..2da1d13ec --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/recipe/RecipeManagerTest.java @@ -0,0 +1,66 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.impl.recipe; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.opamp.client.internal.request.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; + +class RecipeManagerTest { + + @Test + void verifyConstantValues() { + RecipeManager recipeManager = + RecipeManager.create(getFieldsAsList(Field.AGENT_DESCRIPTION, Field.FLAGS)); + + // First run + assertThat(recipeManager.next().build().getFields()) + .containsExactlyInAnyOrder(Field.AGENT_DESCRIPTION, Field.FLAGS); + + // Adding extra fields + recipeManager.next().addField(Field.CAPABILITIES); + + assertThat(recipeManager.next().build().getFields()) + .containsExactlyInAnyOrder(Field.AGENT_DESCRIPTION, Field.FLAGS, Field.CAPABILITIES); + + // Not adding fields for the next build + assertThat(recipeManager.next().build().getFields()) + .containsExactlyInAnyOrder(Field.AGENT_DESCRIPTION, Field.FLAGS); + } + + @Test + void verifyPreviousFields() { + RecipeManager recipeManager = + RecipeManager.create(getFieldsAsList(Field.CAPABILITIES, Field.FLAGS)); + + // Previous build when there's none + assertThat(recipeManager.previous()).isNull(); + + // First build + Collection fields = + recipeManager.next().addField(Field.REMOTE_CONFIG_STATUS).build().getFields(); + assertThat(fields) + .containsExactlyInAnyOrder(Field.CAPABILITIES, Field.FLAGS, Field.REMOTE_CONFIG_STATUS); + assertThat(recipeManager.previous().getFields()).isEqualTo(fields); + + // Merging fields + recipeManager.next().addField(Field.AGENT_DISCONNECT).merge(recipeManager.previous()); + assertThat(recipeManager.next().build().getFields()) + .containsExactlyInAnyOrder( + Field.CAPABILITIES, Field.FLAGS, Field.REMOTE_CONFIG_STATUS, Field.AGENT_DISCONNECT); + } + + @Nonnull + private static List getFieldsAsList(Field... fields) { + return new ArrayList<>(Arrays.asList(fields)); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java index afca33fd0..c00cc0cc0 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java @@ -21,7 +21,7 @@ import com.google.protobuf.CodedOutputStream; import io.opentelemetry.opamp.client.internal.connectivity.websocket.WebSocket; import io.opentelemetry.opamp.client.internal.request.Request; -import io.opentelemetry.opamp.client.internal.response.OpampServerResponseError; +import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -197,7 +197,7 @@ void verifyOnMessage() { requestService.onMessage(createServerToAgentPayload(serverToAgent)); verify(callback).onRequestFailed(throwableCaptor.capture()); verifyNoMoreInteractions(callback); - OpampServerResponseError error = (OpampServerResponseError) throwableCaptor.getValue(); + OpampServerResponseException error = (OpampServerResponseException) throwableCaptor.getValue(); assertThat(error.getMessage()).isEqualTo("A message"); assertThat(scheduler.getScheduledTasks()).isEmpty(); @@ -214,8 +214,8 @@ void verifyOnMessage() { requestService.onMessage(createServerToAgentPayload(serverToAgent)); verify(callback).onRequestFailed(throwableCaptor.capture()); verifyNoMoreInteractions(callback); - OpampServerResponseError unavailableError = - (OpampServerResponseError) throwableCaptor.getValue(); + OpampServerResponseException unavailableError = + (OpampServerResponseException) throwableCaptor.getValue(); assertThat(unavailableError.getMessage()).isEqualTo("Try later"); assertThat(scheduler.getScheduledTasks()).hasSize(1); verify(retryDelay, never()).suggestDelay(any()); @@ -241,8 +241,8 @@ void verifyOnMessage() { requestService.onMessage(createServerToAgentPayload(serverToAgent)); verify(callback).onRequestFailed(throwableCaptor.capture()); verifyNoMoreInteractions(callback); - OpampServerResponseError unavailableErrorWithSuggestedDelay = - (OpampServerResponseError) throwableCaptor.getValue(); + OpampServerResponseException unavailableErrorWithSuggestedDelay = + (OpampServerResponseException) throwableCaptor.getValue(); assertThat(unavailableErrorWithSuggestedDelay.getMessage()).isEmpty(); assertThat(scheduler.getScheduledTasks()).hasSize(1); verify(retryDelay).suggestDelay(suggestedDelay); From 1f5f0d25ec4fdd907904135a9eb651fb0dd5c887 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 22 Jul 2025 19:59:32 +0200 Subject: [PATCH 140/371] deprecate jmx gatherer + document migration (#2034) --- jmx-metrics/README.md | 2 ++ jmx-scraper/README.md | 42 ++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/jmx-metrics/README.md b/jmx-metrics/README.md index 359470774..a9cab0d47 100644 --- a/jmx-metrics/README.md +++ b/jmx-metrics/README.md @@ -1,5 +1,7 @@ # JMX Metric Gatherer +**Deprecation notice**: the JMX Metric Gatherer is deprecated and replaced by [JMX Scraper](../jmx-scraper/), see [migration instructions](../jmx-scraper/README.md#migration-from-jmx-gatherer). + This utility provides an easy framework for gathering and reporting metrics based on queried MBeans from a JMX server. It loads included and/or custom Groovy scripts and establishes a helpful, bound `otel` object with methods for obtaining MBeans and constructing OpenTelemetry instruments: diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 3518f9345..af7d3afea 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -4,8 +4,7 @@ This utility provides a way to query JMX metrics and export them to an OTLP endp The JMX MBeans and their metric mappings are defined in YAML and reuse implementation from [jmx-metrics instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jmx-metrics). -This is currently a work-in-progress component not ready to be used in production. -The end goal is to provide an alternative to the [JMX Gatherer](../jmx-metrics/README.md) utility. +This is an alternative to the [JMX Gatherer](../jmx-metrics/README.md) utility. ## Usage @@ -136,6 +135,45 @@ When doing so, the `java -jar` command can´t be used, we have to provide the cl java -cp scraper.jar:jboss-client.jar io.opentelemetry.contrib.jmxscraper.JmxScraper ``` +## Migration from JMX Gatherer + +The JMX Scraper aims to replace the [JMX Gatherer](../jmx-metrics) tool and thus share most features +and configuration with it. + +Features not supported: + +- Define and capture metrics with custom Groovy definitions with `otel.jmx.groovy.script`, this is now replaced with YAML and `otel.jmx.config` configuration option. +- Ability to export to prometheus collector, only the OTLP exporter is included. + +The YAML-based implementation is provided by [java instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jmx-metrics) +and thus should be used for syntax details and documentation. + +Like with the JMX Gatherer, the selection of provided metrics to use is still done with `otel.jmx.target.system` configuration option. + +However, there is now two distinct sets of metrics to select from using the `otel.jmx.target.source` configuration option: + +- `legacy`: [metrics definitions](./src/main/resources) equivalent to JMX Gatherer definitions to help transition and preserve compatibility +- `instrumentation`: [metrics definitions inherited from instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/), which is now used as a reference for JMX metrics, those also aim to provide better alignment with [metrics semantic conventions](https://opentelemetry.io/docs/specs/semconv/general/metrics/). + +In both cases, the metrics definitions themselves are embedded in the JMX Scraper binary, thus they +will only change if the release version of the JMX Scraper binary changes. + +By default, `otel.jmx.target.source` is `auto`, which means for each value of `otel.jmx.target.system`: + +- Metrics definitions from instrumentation will be used by default, if available. +- Legacy metrics definitions equivalent to JMX Gatherer will be used as fallback. +- Whenever new metrics definitions are being added or modified in instrumentation, those newer definitions will be used. + +There are multiple possible strategies depending on the ability or willingness to embrace change in metrics definitions: + +- To preserve maximum compatibility, using `legacy` is the recommended option, however it means to not benefit from future updates and contributions. +- To only get the most recent definitions, using `instrumentation` ensures that none of the legacy definitions is used, only the reference from instrumentation closer to semconv recommendations, those could still evolve over time. +- To embrace reference definitions whenever they become available, using `auto` is the recommended option, however it means the metrics produced could change when updating the version of JMX Scraper. +- To handle more complex migration strategies or for tight control of metrics definitions, using copies of the YAML metrics definitions and providing them explicitly with `otel.jmx.config` is the recommended option. + +When using `otel.target.source` = `auto` or `legacy`, one or more legacy definitions might be used. If strict compatibility with metrics produced by JMX Gatherer is required it is recommended to review +the [legacy metrics definitions YAML files](./src/main/resources/) as they contain comments on the minor differences with JMX Gatherer Groovy definitions. + ## Component owners - [Jason Plumb](https://github.com/breedx-splk), Splunk From 28d67ea785d885c38edad2f0cc6ea8da3286d7b1 Mon Sep 17 00:00:00 2001 From: Mohamed Asaker Date: Tue, 22 Jul 2025 23:56:12 -0700 Subject: [PATCH 141/371] [aws-xray] Update SamplerRulesApplier to recognize new HTTP/URL semconv. (#1959) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .../contrib/awsxray/SamplingRuleApplier.java | 49 +++-- .../awsxray/SamplingRuleApplierTest.java | 186 ++++++++++++++++++ 2 files changed, 219 insertions(+), 16 deletions(-) diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java index 6387aa0d7..1d97c4aed 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java @@ -19,6 +19,9 @@ import io.opentelemetry.sdk.trace.samplers.Sampler; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.UrlAttributes; import java.time.Duration; import java.util.Collections; import java.util.Date; @@ -57,6 +60,10 @@ final class SamplingRuleApplier { private static final Map XRAY_CLOUD_PLATFORM; + // _OTHER request method: + // https://github.com/open-telemetry/semantic-conventions/blob/main/docs/registry/attributes/http.md?plain=1#L96 + private static final String _OTHER_REQUEST_METHOD = "_OTHER"; + static { Map xrayCloudPlatform = new HashMap<>(); xrayCloudPlatform.put(AWS_EC2, "AWS::EC2::Instance"); @@ -175,25 +182,35 @@ private SamplingRuleApplier( @SuppressWarnings("deprecation") // TODO boolean matches(Attributes attributes, Resource resource) { int matchedAttributes = 0; - String httpTarget = null; - String httpUrl = null; - String httpMethod = null; - String host = null; - for (Map.Entry, Object> entry : attributes.asMap().entrySet()) { - if (entry.getKey().equals(HTTP_TARGET)) { - httpTarget = (String) entry.getValue(); - } else if (entry.getKey().equals(HTTP_URL)) { - httpUrl = (String) entry.getValue(); - } else if (entry.getKey().equals(HTTP_METHOD)) { - httpMethod = (String) entry.getValue(); - } else if (entry.getKey().equals(NET_HOST_NAME)) { - host = (String) entry.getValue(); - } else if (entry.getKey().equals(HTTP_HOST)) { - // TODO (trask) remove support for deprecated http.host attribute - host = (String) entry.getValue(); + String httpTarget = attributes.get(UrlAttributes.URL_PATH); + if (httpTarget == null) { + httpTarget = attributes.get(HTTP_TARGET); + } + + String httpUrl = attributes.get(UrlAttributes.URL_FULL); + if (httpUrl == null) { + httpUrl = attributes.get(HTTP_URL); + } + + String httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD); + if (httpMethod == null) { + httpMethod = attributes.get(HTTP_METHOD); + } + + if (httpMethod != null && httpMethod.equals(_OTHER_REQUEST_METHOD)) { + httpMethod = attributes.get(HttpAttributes.HTTP_REQUEST_METHOD_ORIGINAL); + } + + String host = attributes.get(ServerAttributes.SERVER_ADDRESS); + if (host == null) { + host = attributes.get(NET_HOST_NAME); + if (host == null) { + host = attributes.get(HTTP_HOST); } + } + for (Map.Entry, Object> entry : attributes.asMap().entrySet()) { Matcher matcher = attributeMatchers.get(entry.getKey().getKey()); if (matcher == null) { continue; diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java index bc7bdd3e7..920a5ffd4 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplierTest.java @@ -29,6 +29,9 @@ import io.opentelemetry.sdk.testing.time.TestClock; import io.opentelemetry.sdk.trace.samplers.SamplingDecision; import io.opentelemetry.sdk.trace.samplers.SamplingResult; +import io.opentelemetry.semconv.HttpAttributes; +import io.opentelemetry.semconv.ServerAttributes; +import io.opentelemetry.semconv.UrlAttributes; import io.opentelemetry.semconv.incubating.CloudIncubatingAttributes; import java.io.IOException; import java.io.UncheckedIOException; @@ -72,6 +75,15 @@ class ExactMatch { .put(AttributeKey.longKey("speed"), 10) .build(); + private final Attributes stableSemConvAttributes = + Attributes.builder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "GET") + .put(ServerAttributes.SERVER_ADDRESS, "opentelemetry.io") + .put(UrlAttributes.URL_PATH, "/instrument-me") + .put(AttributeKey.stringKey("animal"), "cat") + .put(AttributeKey.longKey("speed"), 10) + .build(); + // FixedRate set to 1.0 in rule and no reservoir @Test void fixedRateAlwaysSample() { @@ -120,6 +132,21 @@ void matches() { .isTrue(); } + @Test + void matchesURLFullStableSemConv() { + assertThat(applier.matches(stableSemConvAttributes, resource)).isTrue(); + + // url.full works too + assertThat( + applier.matches( + attributes.toBuilder() + .remove(HTTP_TARGET) + .put(UrlAttributes.URL_FULL, "scheme://host:port/instrument-me") + .build(), + resource)) + .isTrue(); + } + @Test void serviceNameNotMatch() { assertThat( @@ -140,6 +167,15 @@ void methodNotMatch() { assertThat(applier.matches(attributes, resource)).isFalse(); } + @Test + void methodStableSemConvNotMatch() { + Attributes attributes = + this.stableSemConvAttributes.toBuilder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "POST") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + } + @Test void hostNotMatch() { // Replacing dot with character makes sure we're not accidentally treating dot as regex @@ -177,6 +213,36 @@ void pathNotMatch() { assertThat(applier.matches(attributes, resource)).isFalse(); } + @Test + void pathStableSemConvNotMatch() { + Attributes attributes = + this.stableSemConvAttributes.toBuilder() + .put(UrlAttributes.URL_PATH, "/instrument-you") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = + this.stableSemConvAttributes.toBuilder() + .remove(UrlAttributes.URL_PATH) + .put(UrlAttributes.URL_FULL, "scheme://host:port/instrument-you") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = + this.stableSemConvAttributes.toBuilder() + .remove(UrlAttributes.URL_PATH) + .put(UrlAttributes.URL_FULL, "scheme://host:port") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + + // Correct path, but we ignore anyways since the URL is malformed per spec, scheme is always + // present. + attributes = + this.stableSemConvAttributes.toBuilder() + .remove(UrlAttributes.URL_PATH) + .put(UrlAttributes.URL_FULL, "host:port/instrument-me") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + } + @Test void attributeNotMatch() { Attributes attributes = @@ -235,6 +301,15 @@ class WildcardMatch { .put(AttributeKey.longKey("speed"), 10) .build(); + private final Attributes stableSemConvAttributes = + Attributes.builder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "GET") + .put(ServerAttributes.SERVER_ADDRESS, "opentelemetry.io") + .put(UrlAttributes.URL_PATH, "/instrument-me?foo=bar&cat=meow") + .put(AttributeKey.stringKey("animal"), "cat") + .put(AttributeKey.longKey("speed"), 10) + .build(); + // FixedRate set to 0.0 in rule and no reservoir @Test void fixedRateNeverSample() { @@ -317,6 +392,36 @@ void methodNotMatch() { assertThat(applier.matches(attributes, resource)).isFalse(); } + @Test + void stableSemConvMethodMatches() { + Attributes attributes = + this.stableSemConvAttributes.toBuilder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "BADGETGOOD") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + attributes = + stableSemConvAttributes.toBuilder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "BADGET") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + attributes = + stableSemConvAttributes.toBuilder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "GETGET") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + } + + @Test + void stableSemConvMethodNotMatch() { + Attributes attributes = + stableSemConvAttributes.toBuilder() + .put(HttpAttributes.HTTP_REQUEST_METHOD, "POST") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = removeAttribute(stableSemConvAttributes, HttpAttributes.HTTP_REQUEST_METHOD); + assertThat(applier.matches(attributes, resource)).isFalse(); + } + @Test void hostMatches() { Attributes attributes = @@ -345,6 +450,56 @@ void hostNotMatch() { assertThat(applier.matches(attributes, resource)).isFalse(); } + @Test + void stableSemConvHostMatches() { + Attributes attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "alpha.opentelemetry.io") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "opfdnqtelemetry.io") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "opentglemetry.io") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "opentglemry.io") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "opentglemrz.io") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + } + + @Test + void stableSemConvHostNotMatch() { + Attributes attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "opentelemetryfio") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "opentgalemetry.io") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = + this.stableSemConvAttributes.toBuilder() + .put(ServerAttributes.SERVER_ADDRESS, "alpha.oentelemetry.io") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = removeAttribute(this.stableSemConvAttributes, ServerAttributes.SERVER_ADDRESS); + assertThat(applier.matches(attributes, resource)).isFalse(); + } + @Test void pathMatches() { Attributes attributes = @@ -368,6 +523,37 @@ void pathNotMatch() { assertThat(applier.matches(attributes, resource)).isFalse(); } + @Test + void pathStableSemConvMatches() { + Attributes attributes = + stableSemConvAttributes.toBuilder() + .put(UrlAttributes.URL_PATH, "/instrument-me?foo=bar&cat=") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + // Deceptive question mark, it's actually a wildcard :-) + attributes = + stableSemConvAttributes.toBuilder() + .put(UrlAttributes.URL_PATH, "/instrument-meafoo=bar&cat=") + .build(); + assertThat(applier.matches(attributes, resource)).isTrue(); + } + + @Test + void pathStableSemConvNotMatch() { + Attributes attributes = + stableSemConvAttributes.toBuilder() + .put(UrlAttributes.URL_PATH, "/instrument-mea?foo=bar&cat=") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = + stableSemConvAttributes.toBuilder() + .put(UrlAttributes.URL_PATH, "foo/instrument-meafoo=bar&cat=") + .build(); + assertThat(applier.matches(attributes, resource)).isFalse(); + attributes = removeAttribute(stableSemConvAttributes, UrlAttributes.URL_PATH); + assertThat(applier.matches(attributes, resource)).isFalse(); + } + @Test void attributeMatches() { Attributes attributes = From 2434f0351bca705333a0e4ef001d2ee7dfb3d799 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 23 Jul 2025 00:29:12 -0700 Subject: [PATCH 142/371] Update change log for upcoming release (#2043) Co-authored-by: Lauri Tulmin --- CHANGELOG.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index deab78620..f13372611 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,60 @@ ## Unreleased +### AWS resources + +- Support for declarative configuration + ([#2014](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2014)) + +### AWS X-Ray SDK support + +- Update SamplerRulesApplier to recognize new HTTP/URL semconv + ([#1959](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/1959)) + +### Azure resources + +- Support for declarative configuration + ([#2014](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2014)) + +### CloudFoundry resources + +- Support for declarative configuration + ([#2014](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2014)) + +### Consistent sampling + +- Refactor ConsistentFixedThresholdSampler to prepare for dynamic threshold support + ([#2018](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2018)) +- ConsistentRateLimitingSampler can fail if used in combination with legacy samplers + ([#2022](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2022)) + +### GCP resources + +- Support for declarative configuration + ([#2014](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2014)) + +### JMX metrics + +- Deprecate JMX Gatherer and provide migration guide to JMX Scraper + ([#2034](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2034)) + +### JMX scraper + +- Update Jetty metrics configuration corresponding to Java Instrumentation 2.18.0 + ([#2033](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2033)) +- Mark as production-ready and remove experimental status + ([#2034](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2034)) + +### Maven extension + +- Support for declarative configuration + ([#2014](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2014)) + +### Resource providers + +- Support for declarative configuration + ([#2014](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2014)) + ## Version 1.47.0 (2025-07-04) ### Disk buffering From d18102b7ba05be4ade560b9c3cd8ebb736220f1c Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Wed, 23 Jul 2025 13:17:04 +0300 Subject: [PATCH 143/371] Update version to 1.49.0-SNAPSHOT (#2048) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 2 ++ version.gradle.kts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f13372611..e6744eff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## Version 1.48.0 (2025-07-23) + ### AWS resources - Support for declarative configuration diff --git a/version.gradle.kts b/version.gradle.kts index 4d162323b..e4f74f0e1 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.48.0-SNAPSHOT" -val alphaVersion = "1.48.0-alpha-SNAPSHOT" +val stableVersion = "1.49.0-SNAPSHOT" +val alphaVersion = "1.49.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From 4c9b3df253645bc3a19d7f5b14c3f7f1c65fcbc6 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 23 Jul 2025 11:10:42 -0700 Subject: [PATCH 144/371] Remove Triagers section from README (#2053) --- README.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/README.md b/README.md index 29bc8d222..080bd8804 100644 --- a/README.md +++ b/README.md @@ -87,12 +87,6 @@ For more information about the maintainer role, see the [community repository](h For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver). -### Triagers - -- All [component owners](https://github.com/open-telemetry/opentelemetry-java-contrib/blob/main/.github.amrom.workers.devponent_owners.yml) are given Triager permissions to this repository. - -For more information about the triager role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#triager). - ### Emeritus maintainers - [Mateusz Rzeszutek](https://github.com/mateuszrzeszutek) From e6101131119eebec28e3320ab2a37efeeb38a021 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 24 Jul 2025 07:45:33 +0300 Subject: [PATCH 145/371] chore(deps): update github/codeql-action action to v3.29.4 (#2054) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 610e2feb4..9efbfe762 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL - uses: github/codeql-action/init@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index e0d440abd..5e018f535 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d6bbdef45e766d081b84a2def353b0055f728d3e # v3.29.3 + uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 with: sarif_file: results.sarif From 87de708de399e394c0837b0013e417cab801c515 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Thu, 24 Jul 2025 09:06:03 +0300 Subject: [PATCH 146/371] Fix flaky aws remote sampler test (#2051) --- .../opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java index 4e5cd13bc..d45e00ad2 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerTest.java @@ -196,7 +196,6 @@ void testJitterTruncation() { .setEndpoint(server.httpUri().toString()) .setPollingInterval(Duration.ofMinutes(5)) .build()) { - assertThat(samplerWithLongerPollingInterval.getNextSamplerUpdateScheduledDuration()).isNull(); await() .untilAsserted( () -> { From bf9042ed43d3fed598b5a79c781ded89f077a2f9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 25 Jul 2025 08:39:55 +0300 Subject: [PATCH 147/371] fix(deps): update dependency org.springframework.boot:spring-boot-starter-parent to v3.5.4 (#2055) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index 15f2d5e25..5417ae121 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.3 + 3.5.4 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index 750e3605b..4f8beee2c 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.3 + 3.5.4 io.opentelemetry.contrib.maven.test From 36b7372794e0e292b2e625d85d8c6c162fe4895c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 28 Jul 2025 08:39:15 +0300 Subject: [PATCH 148/371] fix(deps): update dependency com.github.f4b6a3:uuid-creator to v6.1.1 (#2059) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index ea0541ca8..4603adb07 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -58,6 +58,6 @@ dependencies { api("tools.profiler:async-profiler:4.0") api("com.blogspot.mydailyjava:weak-lock-free:0.18") api("org.agrona:agrona:1.22.0") - api("com.github.f4b6a3:uuid-creator:6.0.0") + api("com.github.f4b6a3:uuid-creator:6.1.1") } } From 2fe5f6f5ac91da65f7cdc11c19f52bd5d53d5031 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 29 Jul 2025 10:26:56 -0700 Subject: [PATCH 149/371] Fix component name (#2064) --- .github.amrom.workers.devponent_owners.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github.amrom.workers.devponent_owners.yml b/.github.amrom.workers.devponent_owners.yml index 39388bf7f..d732f3e07 100644 --- a/.github.amrom.workers.devponent_owners.yml +++ b/.github.amrom.workers.devponent_owners.yml @@ -65,7 +65,7 @@ components: - LikeTheSalad - breedx-splk - jack-berg - prometheus-collector: + prometheus-client-bridge: - jkwatson resource-providers: - breedx-splk From c5b1f7b690151648ae0878229b5c3f244e683f15 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 30 Jul 2025 07:53:52 -0700 Subject: [PATCH 150/371] chore(deps): update github/codeql-action action to v3.29.5 (#2066) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 9efbfe762..6620ca513 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 - name: Initialize CodeQL - uses: github/codeql-action/init@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 5e018f535..6a45e1875 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4 + uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 with: sarif_file: results.sarif From eaafa46d3cbb838d6a49f06a5f0ecd3a42213af0 Mon Sep 17 00:00:00 2001 From: OpenTelemetry Bot <107717825+opentelemetrybot@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:56:54 -0700 Subject: [PATCH 151/371] Add subscript to issue templates (#2068) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 7 +++++++ .github/ISSUE_TEMPLATE/feature_request.yml | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 569597b6a..d268352e1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -57,3 +57,10 @@ body: attributes: label: Additional context description: Any additional information you think may be relevant to this issue. + - type: dropdown + attributes: + label: Tip + description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. + options: + - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). + default: 0 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index 59450b16f..b2756c423 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -48,3 +48,10 @@ body: attributes: label: Additional context description: Add any other context or screenshots about the feature request here. + - type: dropdown + attributes: + label: Tip + description: This element is static, used to render a helpful sub-heading for end-users and community members to help prioritize issues. Please leave as is. + options: + - [React](https://github.blog/news-insights/product-news/add-reactions-to-pull-requests-issues-and-comments/) with 👍 to help prioritize this issue. Please use comments to provide useful context, avoiding `+1` or `me too`, to help us triage it. Learn more [here](https://opentelemetry.io/community/end-user/issue-participation/). + default: 0 From 0d4e5f48e91a7ca5d28f4efef8d16936beaa3ec1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 4 Aug 2025 12:57:44 -0700 Subject: [PATCH 152/371] Auto assign issues based on `component:*` label (#2063) --- .github.amrom.workers.devponent_owners.yml | 6 +- .github/workflows/assign-issue-owners.yml | 75 +++++++++++++++++++++++ 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/assign-issue-owners.yml diff --git a/.github.amrom.workers.devponent_owners.yml b/.github.amrom.workers.devponent_owners.yml index d732f3e07..1e4c90e14 100644 --- a/.github.amrom.workers.devponent_owners.yml +++ b/.github.amrom.workers.devponent_owners.yml @@ -1,14 +1,14 @@ -# this file is used by .github/workflows/assign-reviewers.yml +# this file is used by .github/workflows/assign-reviewers.yml and .github/workflows/assign-issue-owners.yml # # NOTE component owners must be members of the GitHub OpenTelemetry organization # so that they can be added to @open-telemetry/java-contrib-triagers -# which in turn is required for them to be auto-assigned as reviewers by the automation +# which in turn is required for them to be auto-assigned as reviewers and issue assignees by the automation # # NOTE when updating this file, don't forget to update the README.md files in the associated # components also # # NOTE when adding/updating one of the component names, don't forget to update the associated -# `comp:*` labels +# `component:*` labels (used for both PR reviews and issue assignment) components: aws-resources: - wangzlei diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml new file mode 100644 index 000000000..085eeb6fd --- /dev/null +++ b/.github/workflows/assign-issue-owners.yml @@ -0,0 +1,75 @@ +--- +name: Assign issue owners + +on: + issues: + types: [labeled] + +permissions: + contents: read + +jobs: + assign-owners: + permissions: + contents: read + issues: write + runs-on: ubuntu-latest + if: startsWith(github.event.label.name, 'component:') + steps: + - name: Checkout repository + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + + - name: Parse component label and assign owners + uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + with: + script: | + const fs = require('fs'); + const yaml = require('js-yaml'); + + // Extract component name from label + const labelName = context.payload.label.name; + + if (!labelName.startsWith('component:')) { + core.setFailed('Label does not match expected pattern'); + return; + } + + const componentName = labelName.replace('component:', ''); + console.log(`Processing component: ${componentName}`); + + // Read and parse component_owners.yml + const yamlContent = fs.readFileSync('.github.amrom.workers.devponent_owners.yml', 'utf8'); + const data = yaml.load(yamlContent); + + if (!data || !data.components) { + core.setFailed('Invalid component_owners.yml structure'); + return; + } + + const components = data.components; + + if (!(componentName in components)) { + core.setFailed(`Component '${componentName}' not found in component_owners.yml`); + return; + } + + const owners = components[componentName]; + + if (!owners || owners.length === 0) { + core.setFailed(`No owners found for component '${componentName}'`); + return; + } + + console.log(`Found owners: ${owners.join(', ')}`); + + // Assign the issue to the owners + const issueNumber = context.payload.issue.number; + + await github.rest.issues.addAssignees({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issueNumber, + assignees: owners + }); + + console.log(`Successfully assigned issue #${issueNumber} to ${owners.join(', ')}`); From 5be3a42d62173eaf073b0c1702e92fafc8a33d6a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 4 Aug 2025 14:00:29 -0700 Subject: [PATCH 153/371] Fix assign-issue-owners workflow (#2076) --- .github/workflows/assign-issue-owners.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 085eeb6fd..30d3f2aa9 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -19,6 +19,9 @@ jobs: - name: Checkout repository uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + - name: Install js-yaml + run: npm install js-yaml + - name: Parse component label and assign owners uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: From 0471093f72b73cc0482c3c3e033c29825cba3447 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 4 Aug 2025 23:04:48 +0000 Subject: [PATCH 154/371] chore(deps): update actions/checkout digest to 8edcb1b (#2075) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker --- .github/workflows/assign-issue-owners.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 30d3f2aa9..ddeb2bcf9 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -17,7 +17,7 @@ jobs: if: startsWith(github.event.label.name, 'component:') steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Install js-yaml run: npm install js-yaml From 3b73acd05badc9d5d6080e19b1459e4745c71380 Mon Sep 17 00:00:00 2001 From: Matthew Ho <5099946+mcmho@users.noreply.github.com> Date: Fri, 8 Aug 2025 11:45:37 -0700 Subject: [PATCH 155/371] [jmx-scraper] Upload jmx-scraper releases to Maven Central #2071 (#2081) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- jmx-scraper/README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index af7d3afea..044145a96 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -6,6 +6,12 @@ The JMX MBeans and their metric mappings are defined in YAML and reuse implement This is an alternative to the [JMX Gatherer](../jmx-metrics/README.md) utility. +## Release + +This project is released as part of the [OpenTelemetry Java Contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) project. +The latest release is available from [Maven Central](https://central.sonatype.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper) +or can be browsed via [MVN Repository](https://mvnrepository.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper). + ## Usage The general command to invoke JMX scraper is `java -jar scraper.jar `, where `scraper.jar` From 5c0e09d4e1f3350c592a390ad907890e2879da5c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 09:44:40 +0300 Subject: [PATCH 156/371] chore(deps): update actions/download-artifact action to v5 (#2088) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-apply.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 47379ec8a..235851869 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -17,7 +17,7 @@ jobs: pull-requests: write steps: - name: Download patch - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 + uses: actions/download-artifact@634f93cb2916e3fdff6788551b99b062d0335ce0 # v5.0.0 with: run-id: ${{ github.event.workflow_run.id }} path: ${{ runner.temp }} From d84831cbaa8c114965305951a57e68718daa69c9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 09:45:00 +0300 Subject: [PATCH 157/371] fix(deps): update dependency com.linecorp.armeria:armeria-bom to v1.33.0 (#2087) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 4f55b43b5..cc1a7c650 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -138,7 +138,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) - implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.32.5")) + implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.0")) compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") From cd9fc0076a6738edd82d082afae8aa31edb03157 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:33:39 +0300 Subject: [PATCH 158/371] chore(deps): update actions/create-github-app-token action to v2.1.0 (#2086) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-apply.yml | 2 +- .github/workflows/backport.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/prepare-patch-release.yml | 2 +- .github/workflows/prepare-release-branch.yml | 4 ++-- .github/workflows/release.yml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 235851869..3f40db60f 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -32,7 +32,7 @@ jobs: echo "exists=true" >> $GITHUB_OUTPUT fi - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 if: steps.unzip-patch.outputs.exists == 'true' id: otelbot-token with: diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index aa0f5d250..0554ee6bf 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -29,7 +29,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 6a45e1875..732f62642 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: create-token with: # analyzing classic branch protections requires a token with admin read permissions diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml index e4361dea6..8c38a313d 100644 --- a/.github/workflows/prepare-patch-release.yml +++ b/.github/workflows/prepare-patch-release.yml @@ -47,7 +47,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/prepare-release-branch.yml b/.github/workflows/prepare-release-branch.yml index d0db88cd0..5a3774b50 100644 --- a/.github/workflows/prepare-release-branch.yml +++ b/.github/workflows/prepare-release-branch.yml @@ -59,7 +59,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} @@ -116,7 +116,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index af1ca5012..961e585b9 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -221,7 +221,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@df432ceedc7162793a195dd1713ff69aefc7379e # v2.0.6 + - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} From 7a645ed7f9ddcb808cc07941ffd0935d7961de07 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 12:35:20 +0300 Subject: [PATCH 159/371] chore(deps): update open-telemetry/assign-reviewers-action digest to 2f4f06c (#2065) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-reviewers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml index a21d65ed4..5994bd2e1 100644 --- a/.github/workflows/assign-reviewers.yml +++ b/.github/workflows/assign-reviewers.yml @@ -18,6 +18,6 @@ jobs: pull-requests: write # for assigning reviewers runs-on: ubuntu-latest steps: - - uses: open-telemetry/assign-reviewers-action@fcd27c5381c10288b23d423ab85473710a33389e # main + - uses: open-telemetry/assign-reviewers-action@2f4f06ccc561740d5094d9ca5e66dc2392d13e8f # main with: config-file: .github.amrom.workers.devponent_owners.yml From 123b1cc890a6093893fdd7e69f590b42f210477c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 11 Aug 2025 02:40:16 -0700 Subject: [PATCH 160/371] Increase timeout in opamp-client tests (#2079) --- .../client/internal/impl/OpampClientImplTest.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java index ec630dc7e..db2780ed7 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java @@ -209,7 +209,7 @@ void onSuccess_withChangesToReport_notifyCallbackOnMessage() { requestService.sendRequest(); // Await for onMessage call - await().atMost(Duration.ofSeconds(1)).until(() -> callbacks.onMessageCalls.get() == 1); + await().atMost(Duration.ofSeconds(5)).until(() -> callbacks.onMessageCalls.get() == 1); verify(callbacks).onMessage(MessageData.builder().setRemoteConfig(remoteConfig).build()); } @@ -267,7 +267,7 @@ void verifyRemoteConfigStatusSetter() { void onConnectionSuccessful_notifyCallback() { initializeClient(); - await().atMost(Duration.ofSeconds(1)).until(() -> callbacks.onConnectCalls.get() == 1); + await().atMost(Duration.ofSeconds(5)).until(() -> callbacks.onConnectCalls.get() == 1); verify(callbacks).onConnect(); verify(callbacks, never()).onConnectFailed(any()); @@ -311,7 +311,7 @@ void onFailedResponse_withServerErrorData_notifyCallback() { // Force request requestService.sendRequest(); - await().atMost(Duration.ofSeconds(1)).until(() -> callbacks.onErrorResponseCalls.get() == 1); + await().atMost(Duration.ofSeconds(5)).until(() -> callbacks.onErrorResponseCalls.get() == 1); verify(callbacks).onErrorResponse(errorResponse); verify(callbacks, never()).onMessage(any()); @@ -344,7 +344,7 @@ void whenServerProvidesNewInstanceUid_useIt() { enqueueServerToAgentResponse(response); requestService.sendRequest(); - await().atMost(Duration.ofSeconds(1)).until(() -> state.instanceUid.get() != initialUid); + await().atMost(Duration.ofSeconds(5)).until(() -> state.instanceUid.get() != initialUid); assertThat(state.instanceUid.get()).isEqualTo(serverProvidedUid); } @@ -359,7 +359,7 @@ private static AgentToServer getAgentToServerMessage(RecordedRequest request) { private RecordedRequest takeRequest() { try { - return server.takeRequest(1, TimeUnit.SECONDS); + return server.takeRequest(5, TimeUnit.SECONDS); } catch (InterruptedException e) { throw new RuntimeException(e); } From 744469991132c5855f2d260b1cc5dc5dbc71929a Mon Sep 17 00:00:00 2001 From: Lu Shueh Chou Date: Mon, 11 Aug 2025 17:43:19 +0800 Subject: [PATCH 161/371] doc: fix samplers dependency path (#2082) --- samplers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samplers/README.md b/samplers/README.md index 4be98779b..854d44e61 100644 --- a/samplers/README.md +++ b/samplers/README.md @@ -8,7 +8,7 @@ The following samplers support [declarative configuration](https://opentelemetry To use: -* Add a dependency on `io.opentelemetry:opentelemetry-sdk-extension-incubator:` +* Add a dependency on `io.opentelemetry.contrib:opentelemetry-samplers:` * Follow the [instructions](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/README.md#file-configuration) to configure OpenTelemetry with declarative configuration. * Configure the `.tracer_provider.sampler` to include the `rule_based_routing` sampler. From 212cdf5d6faf3a20145c94b984e97ba8dfe00a92 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 13:32:45 +0300 Subject: [PATCH 162/371] fix(deps): update all patch versions (#2080) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/build.yml | 8 ++++---- .github/workflows/codeql.yml | 6 +++--- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 6 +++--- dependencyManagement/build.gradle.kts | 4 ++-- disk-buffering/build.gradle.kts | 2 +- opamp-client/build.gradle.kts | 2 +- 10 files changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index 755959dbf..69e9763bb 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -25,7 +25,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: true diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 260b56660..afef1c49a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -32,7 +32,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Gradle build and test @@ -85,7 +85,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: ${{ github.event_name == 'pull_request' }} - name: Gradle test @@ -107,7 +107,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: ${{ github.event_name == 'pull_request' }} @@ -166,7 +166,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 # skipping release branches because the versions in those branches are not snapshots # (also this skips pull requests) if: ${{ github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-contrib' }} diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 6620ca513..f069746df 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,10 +47,10 @@ jobs: - name: Set up gradle if: matrix.language == 'java' - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Initialize CodeQL - uses: github/codeql-action/init@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 75c7728cd..7d7c04a90 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - - uses: gradle/actions/wrapper-validation@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + - uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 732f62642..4f5823845 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@51f77329afa6477de8c49fc9c7046c15b9a4e79d # v3.29.5 + uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 with: sarif_file: results.sarif diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index a026cb498..a0a0134b1 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -27,7 +27,7 @@ jobs: run: | sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - run: ./gradlew dependencyCheckAnalyze env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 961e585b9..2bac541ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Gradle build run: ./gradlew build @@ -41,7 +41,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Integration test run: ./gradlew integrationTest @@ -124,7 +124,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ac638b010cf58a27ee6c972d7336334ccaf61c96 # v4.4.1 + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Build and publish artifacts run: ./gradlew assemble publishToSonatype closeAndReleaseSonatypeStagingRepository env: diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 4603adb07..7ce9ef36a 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -45,8 +45,8 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.uber.nullaway:nullaway:0.12.7") - api("org.assertj:assertj-core:3.27.3") + api("com.uber.nullaway:nullaway:0.12.8") + api("org.assertj:assertj-core:3.27.4") api("org.awaitility:awaitility:4.3.0") api("org.bouncycastle:bcpkix-jdk15on:1.70") api("org.junit-pioneer:junit-pioneer:1.9.1") diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 8250c1bdf..8d78c9df8 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.github.johnrengelman.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.5" + id("com.squareup.wire") version "5.3.8" } description = "Exporter implementations that store signals on disk" diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index e41d1fff9..416f9d72f 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import java.net.URL plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.5" + id("com.squareup.wire") version "5.3.8" } description = "Client implementation of the OpAMP spec." From f2f65b635d0fcabac864f0bcbe97491dde1a9ffa Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 15:06:14 +0300 Subject: [PATCH 163/371] fix(deps): update errorprone packages to v2.41.0 (#2061) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: Lauri Tulmin --- .../ConsistentRateLimitingSampler.java | 2 +- ...nsistentReservoirSamplingSpanProcessor.java | 10 +++++----- .../ConsistentRateLimitingSampler.java | 2 +- ...tentReservoirSamplingSpanProcessorTest.java | 2 +- .../consistent56/ConsistentSamplerTest.java | 18 +++++++++--------- dependencyManagement/build.gradle.kts | 4 ++-- .../mapping/metrics/MetricDataMapper.java | 4 ++-- .../inferredspans/InferredSpansAutoConfig.java | 13 ++++++------- .../internal/SamplingProfiler.java | 12 ++++++------ .../inferredspans/internal/CallTreeTest.java | 2 +- .../PollingMeterCallbackRegistrar.java | 2 +- .../internal/impl/OpampClientImplTest.java | 2 +- .../service/HttpRequestServiceTest.java | 8 ++++---- .../InterceptableSpanExporterTest.java | 2 +- 14 files changed, 41 insertions(+), 42 deletions(-) diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentRateLimitingSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentRateLimitingSampler.java index 9c2b93f74..14d6dfee4 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentRateLimitingSampler.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentRateLimitingSampler.java @@ -74,7 +74,7 @@ private static final class State { private final double effectiveWindowNanos; private final long lastNanoTime; - public State(double effectiveWindowCount, double effectiveWindowNanos, long lastNanoTime) { + State(double effectiveWindowCount, double effectiveWindowNanos, long lastNanoTime) { this.effectiveWindowCount = effectiveWindowCount; this.effectiveWindowNanos = effectiveWindowNanos; this.lastNanoTime = lastNanoTime; diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessor.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessor.java index c522bf1c6..67defd745 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessor.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessor.java @@ -55,7 +55,7 @@ private static final class ReadableSpanWithPriority { private final int rval; private final long priority; - public static ReadableSpanWithPriority create( + static ReadableSpanWithPriority create( ReadableSpan readableSpan, RandomGenerator randomGenerator) { String otelTraceStateString = readableSpan.getSpanContext().getTraceState().get(OtelTraceState.TRACE_STATE_KEY); @@ -201,7 +201,7 @@ private static final class Reservoir { private final PriorityQueue queue; private final RandomGenerator randomGenerator; - public Reservoir(int reservoirSize, RandomGenerator randomGenerator) { + Reservoir(int reservoirSize, RandomGenerator randomGenerator) { if (reservoirSize < 1) { throw new IllegalArgumentException(); } @@ -211,7 +211,7 @@ public Reservoir(int reservoirSize, RandomGenerator randomGenerator) { this.randomGenerator = randomGenerator; } - public void add(ReadableSpanWithPriority readableSpanWithPriority) { + void add(ReadableSpanWithPriority readableSpanWithPriority) { if (queue.size() < reservoirSize) { queue.add(readableSpanWithPriority); @@ -232,7 +232,7 @@ public void add(ReadableSpanWithPriority readableSpanWithPriority) { } } - public List getResult() { + List getResult() { if (numberOfDiscardedSpansWithMaxDiscardedRValue == 0) { return queue.stream().map(x -> x.readableSpan.toSpanData()).collect(Collectors.toList()); @@ -294,7 +294,7 @@ public List getResult() { return result; } - public boolean isEmpty() { + boolean isEmpty() { return queue.isEmpty(); } } diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java index b58bee96a..0075c5692 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentRateLimitingSampler.java @@ -104,7 +104,7 @@ private static final class State { private final double effectiveDelegateProbability; private final long lastNanoTime; - public State( + State( double effectiveWindowCount, double effectiveWindowNanos, long lastNanoTime, diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java index 476a31983..5eaefd482 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java @@ -125,7 +125,7 @@ public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); } - public void reset() { + void reset() { this.countDownLatch = new CountDownLatch(numberOfSpansToWaitFor); } } diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java index a246e248f..1f2ee35f0 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java @@ -37,42 +37,42 @@ private static class Input { private OptionalLong parentThreshold = OptionalLong.empty(); private OptionalLong parentRandomValue = OptionalLong.empty(); - public void setParentSampled(boolean parentSampled) { + void setParentSampled(boolean parentSampled) { this.parentSampled = parentSampled; } - public void setParentThreshold(long parentThreshold) { + void setParentThreshold(long parentThreshold) { assertThat(parentThreshold).isBetween(0L, 0xffffffffffffffL); this.parentThreshold = OptionalLong.of(parentThreshold); } - public void setParentRandomValue(long parentRandomValue) { + void setParentRandomValue(long parentRandomValue) { assertThat(parentRandomValue).isBetween(0L, 0xffffffffffffffL); this.parentRandomValue = OptionalLong.of(parentRandomValue); } - public Context getParentContext() { + Context getParentContext() { return createParentContext( traceId, spanId, parentThreshold, parentRandomValue, parentSampled); } - public static String getTraceId() { + static String getTraceId() { return traceId; } - public static String getName() { + static String getName() { return name; } - public static SpanKind getSpanKind() { + static SpanKind getSpanKind() { return spanKind; } - public static Attributes getAttributes() { + static Attributes getAttributes() { return attributes; } - public static List getParentLinks() { + static List getParentLinks() { return parentLinks; } } diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7ce9ef36a..111df2bf5 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,8 +27,8 @@ dependencies { api("com.google.auto.service:auto-service-annotations:1.1.1") api("com.google.auto.value:auto-value:1.11.0") api("com.google.auto.value:auto-value-annotations:1.11.0") - api("com.google.errorprone:error_prone_annotations:2.40.0") - api("com.google.errorprone:error_prone_core:2.40.0") + api("com.google.errorprone:error_prone_annotations:2.41.0") + api("com.google.errorprone:error_prone_core:2.41.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") api("io.prometheus:simpleclient:0.16.0") diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapper.java index 1b9f2bf90..46bf32493 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapper.java @@ -766,8 +766,8 @@ private static Attributes protoToAttributes(List source) { } private static final class DataWithType { - public final Data data; - public final MetricDataType type; + final Data data; + final MetricDataType type; private DataWithType(Data data, MetricDataType type) { this.data = data; diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java index 6a4e1ba00..9c8118ec5 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java @@ -90,28 +90,27 @@ private static class PropertiesApplier { private final ConfigProperties properties; - public PropertiesApplier(ConfigProperties properties) { + PropertiesApplier(ConfigProperties properties) { this.properties = properties; } - public void applyBool(String configKey, Consumer funcToApply) { + void applyBool(String configKey, Consumer funcToApply) { applyValue(properties.getBoolean(configKey), funcToApply); } - public void applyInt(String configKey, Consumer funcToApply) { + void applyInt(String configKey, Consumer funcToApply) { applyValue(properties.getInt(configKey), funcToApply); } - public void applyDuration(String configKey, Consumer funcToApply) { + void applyDuration(String configKey, Consumer funcToApply) { applyValue(properties.getDuration(configKey), funcToApply); } - public void applyString(String configKey, Consumer funcToApply) { + void applyString(String configKey, Consumer funcToApply) { applyValue(properties.getString(configKey), funcToApply); } - public void applyWildcards( - String configKey, Consumer> funcToApply) { + void applyWildcards(String configKey, Consumer> funcToApply) { String wildcardListString = properties.getString(configKey); if (wildcardListString != null && !wildcardListString.isEmpty()) { List values = diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java index 23e0dfc99..61e5f75a7 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java @@ -806,7 +806,7 @@ public int compareTo(StackTraceEvent o) { } private static class ActivationEvent { - public static final int SERIALIZED_SIZE = + static final int SERIALIZED_SIZE = Long.SIZE / Byte.SIZE + // timestamp TraceContext.SERIALIZED_LENGTH @@ -826,7 +826,7 @@ private static class ActivationEvent { private long threadId; private boolean activation; - public void activation( + void activation( Span context, long threadId, @Nullable Span previousContext, @@ -835,7 +835,7 @@ public void activation( set(context, threadId, /* activation= */ true, previousContext, nanoTime, clock); } - public void deactivation( + void deactivation( Span context, long threadId, @Nullable Span previousContext, @@ -864,7 +864,7 @@ private void set( this.timestamp = nanoTime; } - public void handle(SamplingProfiler samplingProfiler) { + void handle(SamplingProfiler samplingProfiler) { if (logger.isLoggable(Level.FINE)) { logger.log( Level.FINE, @@ -975,7 +975,7 @@ private void stopProfiling(SamplingProfiler samplingProfiler) { } } - public void serialize(ByteBuffer buf) { + void serialize(ByteBuffer buf) { buf.putLong(timestamp); buf.put(traceContextBuffer); buf.put(previousContextBuffer); @@ -984,7 +984,7 @@ public void serialize(ByteBuffer buf) { buf.put(activation ? (byte) 1 : (byte) 0); } - public void deserialize(ByteBuffer buf) { + void deserialize(ByteBuffer buf) { timestamp = buf.getLong(); buf.get(traceContextBuffer); buf.get(previousContextBuffer); diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java index b3d5df33a..cd824fbc0 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/CallTreeTest.java @@ -1052,7 +1052,7 @@ private static class StackTraceEvent { private final List trace; private final long nanoTime; - public StackTraceEvent(List trace, long nanoTime) { + StackTraceEvent(List trace, long nanoTime) { this.trace = trace; this.nanoTime = nanoTime; diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/PollingMeterCallbackRegistrar.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/PollingMeterCallbackRegistrar.java index 935a81009..fd422d7fa 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/PollingMeterCallbackRegistrar.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/internal/PollingMeterCallbackRegistrar.java @@ -88,7 +88,7 @@ static Iterable of(Runnable callback) { private final Runnable callback; - public PollingIterable(Runnable callback) { + PollingIterable(Runnable callback) { this.callback = callback; } diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java index db2780ed7..764977730 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java @@ -416,7 +416,7 @@ private RecordedRequest initializeClient(ServerToAgent initialResponse) { private static class TestEffectiveConfig extends State.EffectiveConfig { private opamp.proto.EffectiveConfig config; - public TestEffectiveConfig(opamp.proto.EffectiveConfig initialValue) { + TestEffectiveConfig(opamp.proto.EffectiveConfig initialValue) { config = initialValue; } diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java index 9996c4783..c0107673b 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java @@ -352,15 +352,15 @@ public CompletableFuture send(BodyWriter writer, int conten return response; } - public void enqueueResponse(HttpSender.Response response) { + void enqueueResponse(HttpSender.Response response) { enqueueResponseFuture(CompletableFuture.completedFuture(response)); } - public void enqueueResponseFuture(CompletableFuture future) { + void enqueueResponseFuture(CompletableFuture future) { responses.add(future); } - public List getRequests(int size) { + List getRequests(int size) { assertThat(requests).hasSize(size); List immutableRequests = Collections.unmodifiableList(new ArrayList<>(requests)); @@ -369,7 +369,7 @@ public List getRequests(int size) { } private static class RequestParams { - public final int contentLength; + final int contentLength; private RequestParams(int contentLength) { this.contentLength = contentLength; diff --git a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java index 35c3afaaa..a6c177181 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java @@ -82,7 +82,7 @@ void verifySpanFiltering() { private static class ModifiableSpanData extends DelegatingSpanData { private final AttributesBuilder attributes = Attributes.builder(); - protected ModifiableSpanData(SpanData delegate) { + ModifiableSpanData(SpanData delegate) { super(delegate); } From 6773a600a2cdc8ec0ca0ceb7ef3e5d085c729e75 Mon Sep 17 00:00:00 2001 From: "Anuraag (Rag) Agrawal" Date: Mon, 11 Aug 2025 22:44:18 +0900 Subject: [PATCH 164/371] Remove redundant assertions from ConsistentSamplerTest (#2069) --- .../consistent56/ConsistentSamplerTest.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java index 1f2ee35f0..7725bb57a 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplerTest.java @@ -87,10 +87,6 @@ private static class Output { this.parentContext = parentContext; } - boolean getSampledFlag() { - return SamplingDecision.RECORD_AND_SAMPLE.equals(samplingResult.getDecision()); - } - OptionalLong getThreshold() { Span parentSpan = Span.fromContext(parentContext); OtelTraceState otelTraceState = @@ -163,7 +159,6 @@ void testMinThresholdWithoutParentRandomValue() { assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); assertThat(output.getThreshold()).hasValue(0); assertThat(output.getRandomValue()).isNotPresent(); - assertThat(output.getSampledFlag()).isTrue(); } @Test @@ -181,7 +176,6 @@ void testMinThresholdWithParentRandomValue() { assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); assertThat(output.getThreshold()).hasValue(0); assertThat(output.getRandomValue()).hasValue(parentRandomValue); - assertThat(output.getSampledFlag()).isTrue(); } @Test @@ -194,9 +188,8 @@ void testMaxThreshold() { Output output = sample(input, sampler); assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.DROP); - assertThat(output.getThreshold()).isEmpty(); + assertThat(output.getThreshold()).isNotPresent(); assertThat(output.getRandomValue()).isNotPresent(); - assertThat(output.getSampledFlag()).isFalse(); } @Test @@ -216,7 +209,6 @@ void testParentBasedInConsistentMode() { assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); assertThat(output.getThreshold()).hasValue(parentRandomValue); assertThat(output.getRandomValue()).hasValue(parentRandomValue); - assertThat(output.getSampledFlag()).isTrue(); } @Test @@ -232,7 +224,6 @@ void testParentBasedInLegacyMode() { assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); assertThat(output.getThreshold()).isNotPresent(); assertThat(output.getRandomValue()).isNotPresent(); - assertThat(output.getSampledFlag()).isTrue(); } @Test @@ -248,7 +239,6 @@ void testHalfThresholdNotSampled() { assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.DROP); assertThat(output.getThreshold()).isNotPresent(); assertThat(output.getRandomValue()).hasValue(0x7FFFFFFFFFFFFFL); - assertThat(output.getSampledFlag()).isFalse(); } @Test @@ -264,7 +254,6 @@ void testHalfThresholdSampled() { assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); assertThat(output.getThreshold()).hasValue(0x80000000000000L); assertThat(output.getRandomValue()).hasValue(0x80000000000000L); - assertThat(output.getSampledFlag()).isTrue(); } @Test @@ -279,9 +268,7 @@ void testParentViolatingInvariant() { Output output = sample(input, sampler); assertThat(output.samplingResult.getDecision()).isEqualTo(SamplingDecision.RECORD_AND_SAMPLE); - assertThat(output.getThreshold()).hasValue(0x0L); assertThat(output.getRandomValue()).hasValue(0x80000000000000L); - assertThat(output.getSampledFlag()).isTrue(); } } From 89c1afbb8d6660c4243a51f37e9634c4d3ccc98a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 17:42:18 +0300 Subject: [PATCH 165/371] chore(deps): update dependency gradle to v9 (#2074) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker Co-authored-by: Lauri Tulmin --- disk-buffering/build.gradle.kts | 2 +- gcp-auth-extension/build.gradle.kts | 10 +++------- gradle/wrapper/gradle-wrapper.jar | Bin 43764 -> 45457 bytes gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 2 +- jmx-metrics/build.gradle.kts | 2 +- jmx-scraper/build.gradle.kts | 2 +- maven-extension/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 9 files changed, 11 insertions(+), 15 deletions(-) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 8d78c9df8..946639c6f 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -4,7 +4,7 @@ import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer plugins { id("otel.java-conventions") id("otel.publish-conventions") - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" id("com.squareup.wire") version "5.3.8" diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index fd95a653f..f81e5e521 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -1,8 +1,7 @@ plugins { id("otel.java-conventions") id("otel.publish-conventions") - id("com.github.johnrengelman.shadow") - id("org.springframework.boot") version "2.7.18" + id("com.gradleup.shadow") } description = "OpenTelemetry extension that provides GCP authentication support for OTLP exporters" @@ -14,6 +13,8 @@ val agent: Configuration by configurations.creating { } dependencies { + implementation(platform("org.springframework.boot:spring-boot-dependencies:2.7.18")) + annotationProcessor("com.google.auto.service:auto-service") // We use `compileOnly` dependency because during runtime all necessary classes are provided by // javaagent itself. @@ -88,11 +89,6 @@ tasks { assemble { dependsOn(shadowJar) } - - bootJar { - // disable bootJar in build since it only runs as part of test - enabled = false - } } val builtLibsDir = layout.buildDirectory.dir("libs").get().asFile.absolutePath diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 1b33c55baabb587c669f562ae36f953de2481846..8bdaf60c75ab801e22807dde59e12a8735a34077 100644 GIT binary patch delta 37256 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 delta 35551 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 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 78cb6e16a..3e781fbad 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=bd71102213493060956ec229d946beee57158dbd89d0e62b91bca0fa2c5f3531 -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip +distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b +distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index 23d15a936..ef07e0162 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright © 2015-2021 the original authors. +# Copyright © 2015 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index 77ff2c243..b254fc1d8 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id("otel.java-conventions") application - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") id("otel.groovy-conventions") id("otel.publish-conventions") diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index d58fa6490..9ef9a7277 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -1,6 +1,6 @@ plugins { application - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") id("otel.java-conventions") diff --git a/maven-extension/build.gradle.kts b/maven-extension/build.gradle.kts index a2972d0ea..15755f0ce 100644 --- a/maven-extension/build.gradle.kts +++ b/maven-extension/build.gradle.kts @@ -1,6 +1,6 @@ plugins { id("java") - id("com.github.johnrengelman.shadow") + id("com.gradleup.shadow") id("otel.java-conventions") id("otel.publish-conventions") } diff --git a/settings.gradle.kts b/settings.gradle.kts index bc4b5a1de..ed7366ed7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - id("com.github.johnrengelman.shadow") version "8.1.1" + id("com.gradleup.shadow") version "9.0.0-rc3" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("com.gradle.develocity") version "4.1" } From 05b02ba5d1d2eae784d62f4e28f4d1b0295a920f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 11 Aug 2025 18:06:12 -0700 Subject: [PATCH 166/371] fix(deps): update all patch versions (#2092) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-apply.yml | 2 +- .github/workflows/backport.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/prepare-patch-release.yml | 2 +- .github/workflows/prepare-release-branch.yml | 4 ++-- .github/workflows/release.yml | 2 +- micrometer-meter-provider/build.gradle.kts | 4 ++-- settings.gradle.kts | 2 +- 8 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 3f40db60f..0e47c3512 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -32,7 +32,7 @@ jobs: echo "exists=true" >> $GITHUB_OUTPUT fi - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 if: steps.unzip-patch.outputs.exists == 'true' id: otelbot-token with: diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 0554ee6bf..a82067c53 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -29,7 +29,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 4f5823845..55955835c 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: create-token with: # analyzing classic branch protections requires a token with admin read permissions diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml index 8c38a313d..d923fc218 100644 --- a/.github/workflows/prepare-patch-release.yml +++ b/.github/workflows/prepare-patch-release.yml @@ -47,7 +47,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/prepare-release-branch.yml b/.github/workflows/prepare-release-branch.yml index 5a3774b50..c6cc32de0 100644 --- a/.github/workflows/prepare-release-branch.yml +++ b/.github/workflows/prepare-release-branch.yml @@ -59,7 +59,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} @@ -116,7 +116,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2bac541ea..77cf6b26a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -221,7 +221,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@0f859bf9e69e887678d5bbfbee594437cb440ffe # v2.1.0 + - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index 9758a7415..6d585cca5 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.15.2") + testImplementation("io.micrometer:micrometer-core:1.15.3") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.15.2") + implementation("io.micrometer:micrometer-registry-prometheus:1.15.3") } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index ed7366ed7..f83b17cd2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - id("com.gradleup.shadow") version "9.0.0-rc3" + id("com.gradleup.shadow") version "9.0.1" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("com.gradle.develocity") version "4.1" } From cab11c190fe9501a0554d3db1d8a6a03e5f6ded4 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 12 Aug 2025 17:30:33 +0200 Subject: [PATCH 167/371] async profiler 4.1 update (#2096) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- .../internal/asyncprofiler/JfrParser.java | 5 +++++ .../internal/asyncprofiler/JfrParserTest.java | 2 +- .../src/test/resources/recording.jfr | Bin 64087 -> 34570 bytes 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 111df2bf5..1df9b2a7c 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -55,7 +55,7 @@ dependencies { api("org.testcontainers:kafka:1.21.3") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") - api("tools.profiler:async-profiler:4.0") + api("tools.profiler:async-profiler:4.1") api("com.blogspot.mydailyjava:weak-lock-free:0.18") api("org.agrona:agrona:1.22.0") api("com.github.f4b6a3:uuid-creator:6.1.1") diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParser.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParser.java index c8ee4013f..44c6f9c13 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParser.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParser.java @@ -199,6 +199,10 @@ private void parseConstantPool() throws IOException { case ContentTypeId.CONTENT_SYMBOL: readSymbolConstants(count); break; + case ContentTypeId.CONTENT_STRING: + // ignore empty string + bufferedFile.skip(2); + break; default: throw new IllegalStateException("Unhandled constant pool type: " + typeId); } @@ -499,5 +503,6 @@ private ContentTypeId() {} static final int CONTENT_FRAME_TYPE = 24; static final int CONTENT_GC_WHEN = 32; static final int CONTENT_PACKAGE = 30; + static final int CONTENT_STRING = 20; } } diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParserTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParserTest.java index 0bf2d042e..60273534b 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParserTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/asyncprofiler/JfrParserTest.java @@ -58,6 +58,6 @@ void name() throws Exception { } stackFrames.clear(); }); - assertThat(stackTraces.get()).isEqualTo(66); + assertThat(stackTraces.get()).isEqualTo(92); } } diff --git a/inferred-spans/src/test/resources/recording.jfr b/inferred-spans/src/test/resources/recording.jfr index b44c32414d50f4258051749574a693855aab7e25..8f58d93501c9cf7829f33b2f1acce7e142c25abe 100644 GIT binary patch literal 34570 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 OyIi!UWA*BltN#yABQksd literal 64087 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 Date: Wed, 13 Aug 2025 07:49:04 +0300 Subject: [PATCH 168/371] chore(deps): update github/codeql-action action to v3.29.9 (#2098) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f069746df..8433a829a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Initialize CodeQL - uses: github/codeql-action/init@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 + uses: github/codeql-action/init@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 + uses: github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 55955835c..5b627ecee 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@76621b61decf072c1cee8dd1ce2d2a82d33c17ed # v3.29.8 + uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 with: sarif_file: results.sarif From ea4b55602dee49fe4885778c82f5f0d40e924a5c Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Wed, 13 Aug 2025 19:13:16 +0300 Subject: [PATCH 169/371] Replace okhttp with okhttp-jvm in published maven pom (#2094) --- .../main/kotlin/otel.publish-conventions.gradle.kts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts index 7b922edc8..070edf183 100644 --- a/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.publish-conventions.gradle.kts @@ -57,6 +57,17 @@ publishing { developerConnection.set("scm:git:git@github.com:open-telemetry/opentelemetry-java-contrib.git") url.set("git@github.com:open-telemetry/opentelemetry-java-contrib.git") } + + withXml { + // Since 5.0 okhttp uses gradle metadata to choose either okhttp-jvm or okhttp-android. + // This does not work for maven builds that don't understand gradle metadata. They end up + // using the okhttp artifact that is an empty jar. Here we replace usages of okhttp with + // okhttp-jvm so that maven could get the actual okhttp dependency instead of the empty jar. + var result = asString() + var modified = result.toString().replace(">okhttp<", ">okhttp-jvm<") + result.clear() + result.append(modified) + } } } } From b1abab5bf6bbb227d2658dc64158043fd1654f80 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 14 Aug 2025 07:55:11 -0700 Subject: [PATCH 170/371] Remove unnecessary gradle option (#2090) --- gradle.properties | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index ef53d84af..0b5e135ed 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,3 @@ org.gradle.priority=low # Gradle default is 256m which causes issues with our build - https://docs.gradle.org/current/userguide/build_environment.html#sec:configuring_jvm_memory org.gradle.jvmargs=-XX:MaxMetaspaceSize=512m - -# Workaround https://youtrack.jetbrains.com/issue/KT-47152 -# We don't have enough kotlin code to care about incremental compilation anyways. -kotlin.incremental=false From 40ca061d9e641bbf3d9611872b7c0d6350f3a928 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 14 Aug 2025 10:02:42 -0700 Subject: [PATCH 171/371] Auto-update Java version used in tests (#2097) --- .github/renovate.json5 | 14 +++++++++++++- .github/workflows/build.yml | 4 ++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 4f7743a37..b45b3df4d 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -6,6 +6,7 @@ ], ignorePresets: [ ':ignoreModulesAndTests', // needed to keep maven-extension test pom files up-to-date + 'workarounds:javaLTSVersions', // Allow all Java major versions, not just LTS ], prHourlyLimit: 5, // we have a large number of parallel runners labels: [ @@ -170,11 +171,22 @@ customType: 'regex', datasourceTemplate: 'npm', managerFilePatterns: [ - '/^.github/workflows//', + '.github/workflows/**' ], matchStrings: [ 'npx (?[^@]+)@(?[^\\s]+)', ], }, + { + customType: 'regex', + datasourceTemplate: 'java-version', + managerFilePatterns: [ + '.github/workflows/**' + ], + matchStrings: [ + '(?\\d+) # renovate: datasource=java-version', + ], + depNameTemplate: 'java', + }, ], } diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index afef1c49a..2d7d0abcc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: - 11 - 17 - 21 - - 23 + - 23 # renovate: datasource=java-version # macos-latest drops support for java 8 temurin. Run java 8 on macos-13. Run java 11, 17, 21 on macos-latest. exclude: - os: macos-latest @@ -66,7 +66,7 @@ jobs: - os: macos-13 test-java-version: 21 - os: macos-13 - test-java-version: 23 + test-java-version: 23 # renovate: datasource=java-version steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 From 567e54e4c983810661758f0a095fd7f7a8e28605 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Thu, 14 Aug 2025 11:40:15 -0700 Subject: [PATCH 172/371] Add ibm-mq-metrics (#1960) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker Co-authored-by: Lauri Tulmin --- .github.amrom.workers.devponent_owners.yml | 3 + README.md | 1 + ibm-mq-metrics/Makefile | 84 ++ ibm-mq-metrics/README.md | 228 +++++ ibm-mq-metrics/build.gradle.kts | 79 ++ ibm-mq-metrics/docs/metrics.md | 801 ++++++++++++++++++ ibm-mq-metrics/model/attributes.yaml | 75 ++ ibm-mq-metrics/model/metrics.yaml | 611 +++++++++++++ ibm-mq-metrics/model/registry_manifest.yaml | 3 + .../mq/integration/tests/JakartaPutGet.java | 293 +++++++ .../mq/integration/tests/TestWMQMonitor.java | 58 ++ .../tests/WMQMonitorIntegrationTest.java | 284 +++++++ .../resources/conf/test-config.yml | 197 +++++ .../resources/conf/test-queuemgr-config.yml | 170 ++++ .../io/opentelemetry/ibm/mq/WmqContext.java | 106 +++ .../io/opentelemetry/ibm/mq/WmqMonitor.java | 209 +++++ .../ibm/mq/config/ExcludeFilters.java | 85 ++ .../ibm/mq/config/QueueManager.java | 257 ++++++ .../ibm/mq/config/ResourceFilters.java | 31 + .../ibm/mq/metrics/IbmMqAttributes.java | 51 ++ .../opentelemetry/ibm/mq/metrics/Metrics.java | 424 +++++++++ .../ibm/mq/metrics/MetricsConfig.java | 213 +++++ .../ChannelMetricsCollector.java | 231 +++++ .../ibm/mq/metricscollector/FilterType.java | 14 + .../InquireChannelCmdCollector.java | 149 ++++ .../InquireQCmdCollector.java | 69 ++ .../InquireQStatusCmdCollector.java | 74 ++ .../InquireQueueManagerCmdCollector.java | 76 ++ .../InquireTStatusCmdCollector.java | 136 +++ .../ListenerMetricsCollector.java | 113 +++ .../ibm/mq/metricscollector/MessageBuddy.java | 75 ++ .../mq/metricscollector/MessageFilter.java | 77 ++ .../MetricsCollectorContext.java | 101 +++ .../PerformanceEventQueueCollector.java | 131 +++ .../QueueCollectionBuddy.java | 311 +++++++ .../QueueCollectorSharedState.java | 25 + .../QueueManagerEventCollector.java | 111 +++ .../QueueManagerMetricsCollector.java | 102 +++ .../QueueMetricsCollector.java | 62 ++ .../ReadConfigurationEventQueueCollector.java | 142 ++++ .../ResetQStatsCmdCollector.java | 56 ++ .../TopicMetricsCollector.java | 26 + .../ibm/mq/opentelemetry/Config.java | 83 ++ .../ibm/mq/opentelemetry/ConfigWrapper.java | 129 +++ .../ibm/mq/opentelemetry/Main.java | 87 ++ .../io/opentelemetry/ibm/mq/util/WmqUtil.java | 78 ++ .../ChannelMetricsCollectorTest.java | 227 +++++ .../InquireChannelCmdCollectorTest.java | 99 +++ .../ListenerMetricsCollectorTest.java | 102 +++ .../ibm/mq/metricscollector/MetricAssert.java | 38 + .../QueueCollectionBuddyTest.java | 339 ++++++++ .../QueueManagerMetricsCollectorTest.java | 121 +++ .../TopicMetricsCollectorTest.java | 113 +++ .../ibm/mq/opentelemetry/ConfigTest.java | 54 ++ .../mq/opentelemetry/ConfigWrapperTest.java | 51 ++ .../src/test/resources/conf/config.yml | 217 +++++ .../registry/java/IbmMqAttributes.java.j2 | 27 + .../templates/registry/java/Metrics.java.j2 | 40 + .../registry/java/MetricsConfig.java.j2 | 36 + .../templates/registry/java/weaver.yaml | 10 + .../registry/markdown/attribute_macros.j2 | 36 + .../markdown/attribute_namespace.md.j2 | 51 ++ .../registry/markdown/attribute_table.j2 | 13 + .../registry/markdown/body_field_table.j2 | 18 + .../registry/markdown/enum_macros.j2 | 30 + .../registry/markdown/event_macros.j2 | 16 + .../registry/markdown/examples_macros.j2 | 17 + .../registry/markdown/metric_macros.j2 | 8 + .../registry/markdown/metric_table.j2 | 7 + .../templates/registry/markdown/metrics.md.j2 | 14 + .../templates/registry/markdown/notes.j2 | 9 + .../templates/registry/markdown/readme.md.j2 | 42 + .../registry/markdown/requirement.j2 | 9 + .../registry/markdown/resource_macros.j2 | 16 + .../registry/markdown/sampling_macros.j2 | 7 + .../templates/registry/markdown/snippet.md.j2 | 32 + .../templates/registry/markdown/stability.j2 | 11 + .../templates/registry/markdown/weaver.yaml | 4 + ibm-mq-metrics/weaver.Dockerfile | 6 + settings.gradle.kts | 1 + 80 files changed, 8542 insertions(+) create mode 100644 ibm-mq-metrics/Makefile create mode 100644 ibm-mq-metrics/README.md create mode 100644 ibm-mq-metrics/build.gradle.kts create mode 100644 ibm-mq-metrics/docs/metrics.md create mode 100644 ibm-mq-metrics/model/attributes.yaml create mode 100644 ibm-mq-metrics/model/metrics.yaml create mode 100644 ibm-mq-metrics/model/registry_manifest.yaml create mode 100644 ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java create mode 100644 ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java create mode 100644 ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java create mode 100644 ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml create mode 100644 ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java create mode 100644 ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java create mode 100644 ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java create mode 100644 ibm-mq-metrics/src/test/resources/conf/config.yml create mode 100644 ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 create mode 100644 ibm-mq-metrics/templates/registry/java/Metrics.java.j2 create mode 100644 ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 create mode 100644 ibm-mq-metrics/templates/registry/java/weaver.yaml create mode 100644 ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/event_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/metric_table.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/notes.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/readme.md.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/requirement.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/stability.j2 create mode 100644 ibm-mq-metrics/templates/registry/markdown/weaver.yaml create mode 100644 ibm-mq-metrics/weaver.Dockerfile diff --git a/.github.amrom.workers.devponent_owners.yml b/.github.amrom.workers.devponent_owners.yml index 1e4c90e14..6b74234ad 100644 --- a/.github.amrom.workers.devponent_owners.yml +++ b/.github.amrom.workers.devponent_owners.yml @@ -90,3 +90,6 @@ components: opamp-client: - LikeTheSalad - jackshirazi + ibm-mq-metrics: + - breedx-splk + - atoulme diff --git a/README.md b/README.md index 080bd8804..c42ea7e27 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ feature or via instrumentation, this project is hopefully for you. | alpha | [GCP Authentication Extension](./gcp-auth-extension/README.md) | | beta | [GCP Resources](./gcp-resources/README.md) | | beta | [Inferred Spans](./inferred-spans/README.md) | +| alpha | [IBM MQ Metrics](./ibm-mq-metrics/README.md) | | alpha | [JFR Connection](./jfr-connection/README.md) | | alpha | [JFR Events](./jfr-events/README.md) | | alpha | [JMX Metric Gatherer](./jmx-metrics/README.md) | diff --git a/ibm-mq-metrics/Makefile b/ibm-mq-metrics/Makefile new file mode 100644 index 000000000..d6ad8e42a --- /dev/null +++ b/ibm-mq-metrics/Makefile @@ -0,0 +1,84 @@ +# From where to resolve the containers (e.g. "otel/weaver"). +WEAVER_CONTAINER_REPOSITORY=docker.io +# Versioned, non-qualified references to containers used in this Makefile. +# These are parsed from dependencies.Dockerfile so dependabot will autoupdate +# the versions of docker files we use. +VERSIONED_WEAVER_CONTAINER_NO_REPO=$(shell cat weaver.Dockerfile | awk '$$4=="weaver" {print $$2}') +# Versioned, non-qualified references to containers used in this Makefile. +WEAVER_CONTAINER=$(WEAVER_CONTAINER_REPOSITORY)/$(VERSIONED_WEAVER_CONTAINER_NO_REPO) + +# Next - we want to run docker as our local file user, so generated code is not +# owned by root, and we don't give unnecessary access. +# +# Determine if "docker" is actually podman +DOCKER_VERSION_OUTPUT := $(shell docker --version 2>&1) +DOCKER_IS_PODMAN := $(shell echo $(DOCKER_VERSION_OUTPUT) | grep -c podman) +ifeq ($(DOCKER_IS_PODMAN),0) + DOCKER_COMMAND := docker +else + DOCKER_COMMAND := podman +endif +DOCKER_RUN=$(DOCKER_COMMAND) run +DOCKER_USER=$(shell id -u):$(shell id -g) +DOCKER_USER_IS_HOST_USER_ARG=-u $(DOCKER_USER) +ifeq ($(DOCKER_COMMAND),podman) + # On podman, additional arguments are needed to make "-u" work + # correctly with the host user ID and host group ID. + # + # Error: OCI runtime error: crun: setgroups: Invalid argument + DOCKER_USER_IS_HOST_USER_ARG=--userns=keep-id -u $(DOCKER_USER) +endif + +.PHONY: generate-docs +generate-docs: + mkdir -p docs + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/docs,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry generate \ + --registry=/home/weaver/model \ + markdown \ + --future \ + /home/weaver/target + +.PHONY: check +check: + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/docs,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry check \ + --registry=/home/weaver/model + +.PHONY: generate-java +generate-java: + mkdir -p src/main/java/io/opentelemetry/ibm/mq/metrics + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/src/main/java/io/opentelemetry/ibm/mq/metrics,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry generate \ + --registry=/home/weaver/model \ + java \ + --future \ + /home/weaver/target + +.PHONY: generate-yaml +generate-yaml: + $(DOCKER_RUN) --rm \ + $(DOCKER_USER_IS_HOST_USER_ARG) \ + --mount 'type=bind,source=$(PWD)/model,target=/home/weaver/model,readonly' \ + --mount 'type=bind,source=$(PWD)/templates,target=/home/weaver/templates,readonly' \ + --mount 'type=bind,source=$(PWD)/,target=/home/weaver/target' \ + ${WEAVER_CONTAINER} registry generate \ + --registry=/home/weaver/model \ + yaml \ + --future \ + /home/weaver/target + +.PHONY: generate +generate: generate-docs generate-yaml generate-java diff --git a/ibm-mq-metrics/README.md b/ibm-mq-metrics/README.md new file mode 100644 index 000000000..5c75acd32 --- /dev/null +++ b/ibm-mq-metrics/README.md @@ -0,0 +1,228 @@ +# IBM MQ Metrics + +:warning: This software is under development. + +## Use case + +IBM MQ, formerly known as WebSphere MQ (message queue) series, is an IBM software for +program-to-program messaging across multiple platforms. + +The IBM MQ metrics utility here can monitor multiple queues managers and their resources, +namely queues, topics, channels and listeners The metrics are extracted out using the +[PCF command messages](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm). + +The metrics for queue manager, queue, topic, channel and listener can be configured. + +The MQ Monitor is compatible with IBM MQ version 7.x, 8.x and 9.x. + +## Prerequisites + +This software requires compilation with Java 11. +It targets language level 8 and outputs java 8 class files. + +The extension has a dependency on the following jar's depending on IBM MQ version: + +* v8.0.0 and above + +``` +com.ibm.mq.allclient.jar +``` + +* For other versions + +``` +com.ibm.mq.commonservices.jar +com.ibm.mq.jar +com.ibm.mq.jmqi.jar +com.ibm.mq.headers.jar +com.ibm.mq.pcf.jar +dhbcore.jar +connector.jar +``` + +These jar files are typically found in ```/opt/mqm/java/lib``` on a UNIX server but may be +found in an alternate location depending upon your environment. + +In case of **CLIENT** transport type, IBM MQ Client must be installed to get the MQ jars. +[The IBM MQ Client jars can be downloaded here](https://developer.ibm.com/messaging/mq-downloads/). + +### MQ monitoring configuration + +This software reads events from event queues associated with the queue manager: + +* `SYSTEM.ADMIN.PERFM.EVENT`: Performance events, such as low, high, and full queue depth events. +* `SYSTEM.ADMIN.QMGR.EVENT`: Authority events +* `SYSTEM.ADMIN.CONFIG.EVENT`: Configuration events + +Please turn on those events to take advantage of this monitoring. + +## Build + +Build the package with: + +```shell +cd ibm-mq-metrics +../gradlew shadowJar +``` + +Note: Due to restrictive licensing, this uber-jar (fat-jar) does not include the IBM client jar. + +## Run + +Run the standalone jar alongside the IBM jar: + +```shell +cd ibm-mq-metrics +java \ + -Djavax.net.ssl.keyStore=key.jks \ + -Djavax.net.ssl.keyStorePassword= \ + -Djavax.net.ssl.trustStore=key.jks \ + -Djavax.net.ssl.trustStorePassword= \ + -cp build/libs/opentelemetry-ibm-mq-monitoring--all.jar:lib/com.ibm.mq.allclient.jar \ + io.opentelemetry.ibm.mq.opentelemetry.Main \ + ./my-config.yml +``` + +## Connection + +There are two transport modes in which this extension can be run: + +* **Binding** : Requires WMQ Extension to be deployed in machine agent on the same machine where + WMQ server is installed. +* **Client** : In this mode, the WMQ extension is installed on a different host than the IBM MQ + server. Please install the [IBM MQ Client](https://developer.ibm.com/messaging/mq-downloads/) + for this mode to get the necessary jars as mentioned previously. + +If this extension is configured for **CLIENT** transport type + +1. Please make sure the MQ's host and port is accessible. +2. Credentials of user with correct access rights would be needed in config.yml. +3. If the hosting OS for IBM MQ is Windows, Windows user credentials will be needed. + +If you are in **Bindings** mode, please make sure to start the MA process under a user which has +the following permissions on the broker. Similarly, for **Client** mode, please provide the user +credentials in config.yml which have permissions listed below. + +The user connecting to the queueManager should have the inquire, get, put (since PCF responses +cause dynamic queues to be created) permissions. For metrics that execute MQCMD_RESET_Q_STATS +command, chg permission is needed. + +### SSL Support + +_Note: The following is only needed for versions of Java 8 before 8u161._ + +1. Configure the IBM SSL Cipher Suite in the config.yml. + Note that, to use some CipherSuites the unrestricted policy needs to be configured in JRE. + Please visit [this link](http://www.ibm.com/support/knowledgecenter/SSYKE2_8.0.0/com.ibm.java.security.component.80.doc/security-component/sdkpolicyfiles.html) + for more details. For Oracle JRE, please update with [JCE Unlimited Strength Jurisdiction Policy](http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html). + The download includes a readme file with instructions on how to apply these files to JRE. + +2. Please add the following JVM arguments to the MA start up command or script. + + ```-Dcom.ibm.mq.cfg.useIBMCipherMappings=false``` (If you are using IBM Cipher Suites, set the + flag to true. Please visit [this link](http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm) for more details. + ) +3. To configure SSL, the MA's trust store and keystore needs to be setup with the JKS filepath. + They can be passed either as Machine Agent JVM arguments or configured in config.yml (sslConnection)
+ + a. Machine Agent JVM arguments as follows: + + ```-Djavax.net.ssl.trustStore=```
+ ```-Djavax.net.ssl.trustStorePassword=```
+ ```-Djavax.net.ssl.keyStore=```
+ ```-Djavax.net.ssl.keyStorePassword=```
+ + b. sslConnection in config.yml, configure the trustStorePassword. Same holds for keyStore configuration as well. + + ``` + sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + ``` + +## Configuration + +**Note** : Please make sure to not use tab (\t) while editing yaml files. You may want to validate +the yaml file using a [yaml validator](https://jsonformatter.org/yaml-validator). Configure the monitor by copying and editing the +config.yml file in src/main/resources/config.yml. + +1. Configure the queueManagers with appropriate fields and filters. You can configure multiple + queue managers in one configuration file. +2. To run the extension at a frequency > 1 minute, please configure the taskSchedule section. + Refer to the [Task Schedule](https://community.appdynamics.com/t5/Knowledge-Base/Task-Schedule-for-Extensions/ta-p/35414) doc for details. + +### Monitoring Workings - Internals + +This software extracts metrics through [PCF framework](https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q019990_.htm). +[A complete list of PCF commands are listed here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q086870_.htm). +Each queue manager has an administration queue with a standard queue name and +the extension sends PCF command messages to that queue. On Windows and Unix platforms, the PCF +commands are sent is always sent to the SYSTEM.ADMIN.COMMAND.QUEUE queue. +[More details mentioned here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm) + +By default, the PCF responses are sent to the SYSTEM.DEFAULT.MODEL.QUEUE. Using this queue causes +a temporary dynamic queue to be created. You can override the default here by using the +`modelQueueName` and `replyQueuePrefix` fields in the config.yml. +[More details mentioned here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm) + +## Metrics + +See [docs/metrics.md](docs/metrics.md). + +## Troubleshooting + +1. Error `Completion Code '2', Reason '2495'` + Normally this error occurs if the environment variables are not set up correctly for this extension to work MQ in Bindings Mode. + + If you are seeing `Failed to load the WebSphere MQ native JNI library: 'mqjbnd'`, please add the following jvm argument when starting the MA. + + -Djava.library.path=\ For eg. on Unix it could -Djava.library.path=/opt/mqm/java/lib64 for 64-bit or -Djava.library.path=/opt/mqm/java/lib for 32-bit OS + + Sometimes you also have run the setmqenv script before using the above jvm argument to start the machine agent. + + . /opt/mqm/bin/setmqenv -s + + For more details, please check this [doc](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.1.0/com.ibm.mq.doc/zr00610_.htm) + + This might occur due to various reasons ranging from incorrect installation to applying + IBM Fix Packs, but most of the time it happens when you are trying to connect in `Bindings` + mode and machine agent is not on the same machine on which WMQ server is running. If you want + to connect to WMQ server from a remote machine then connect using `Client` mode. + + Another way to get around this issue is to avoid using the Bindings mode. Connect using CLIENT + transport type from a remote box. + +2. Error `Completion Code '2', Reason '2035'` + This could happen for various reasons but for most of the cases, for **Client** mode the + user specified in config.yml is not authorized to access the queue manager. Also sometimes + even if userid and password are correct, channel auth (CHLAUTH) for that queue manager blocks + traffics from other ips, you need to contact admin to provide you access to the queue manager. + For Bindings mode, please make sure that the MA is owned by a mqm user. + +3. `MQJE001: Completion Code '2', Reason '2195'` + This could happen in **Client** mode. Please make sure that the IBM MQ dependency jars are correctly referenced in classpath of monitor.xml + +4. `MQJE001: Completion Code '2', Reason '2400'` + This could happen if unsupported cipherSuite is provided or JRE not having/enabled unlimited jurisdiction policy files. Please check SSL Support section. + +5. If you are seeing `NoClassDefFoundError` or `ClassNotFoundException` error for any of the MQ dependency even after providing correct path in monitor.xml, then you can also try copying all the required jars in WMQMonitor (MAHome/monitors/WMQMonitor) folder and provide classpath in monitor.xml like below + + ``` + opentelemetry-ibm-mq-monitoring--all.jar;com.ibm.mq.allclient.jar + ``` + + OR + + ``` + opentelemetry-ibm-mq-monitoring--all.jar;com.ibm.mq.jar;com.ibm.mq.jmqi.jar;com.ibm.mq.commonservices.jar;com.ibm.mq.headers.jar;com.ibm.mq.pcf.jar;connector.jar;dhbcore.jar + ``` + +## Component Owners + +- [Antoine Toulme Sharma](https://github.com/atoulme), Splunk +- [Jason Plumb](https://github.com/breedx-splk), Splunk + +Learn more about component owners in [component_owners.yml](../.github.amrom.workers.devponent_owners.yml). diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts new file mode 100644 index 000000000..00a2c1988 --- /dev/null +++ b/ibm-mq-metrics/build.gradle.kts @@ -0,0 +1,79 @@ +plugins { + application + id("com.gradleup.shadow") + id("otel.java-conventions") + id("otel.publish-conventions") +} + +description = "IBM-MQ metrics" +otelJava.moduleName.set("io.opentelemetry.contrib.ibm-mq-metrics") +application.mainClass.set("io.opentelemetry.ibm.mq.opentelemetry.Main") + +sourceSets { + create("integrationTest") { + compileClasspath += sourceSets.main.get().output + runtimeClasspath += sourceSets.main.get().output + } +} + +val integrationTestImplementation by configurations.getting { + extendsFrom(configurations.implementation.get()) +} +val integrationTestRuntimeOnly by configurations.getting + +configurations["integrationTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) + +val ibmClientJar: Configuration by configurations.creating { + isCanBeResolved = true + isCanBeConsumed = false +} + +dependencies { + api("com.google.code.findbugs:jsr305:3.0.2") + api("io.swagger:swagger-annotations:1.6.16") + api("org.jetbrains:annotations:26.0.2") + api("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1") + api("org.yaml:snakeyaml:2.4") + api("com.fasterxml.jackson.core:jackson-databind:2.19.0") + api("io.opentelemetry:opentelemetry-sdk") + api("io.opentelemetry:opentelemetry-exporter-otlp") + api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + api("org.slf4j:slf4j-api:2.0.7") + testImplementation("com.google.guava:guava") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.50.0") + integrationTestImplementation("org.assertj:assertj-core:3.27.3") + integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") + integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.50.0") + integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.2.0") + integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") + integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") + integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0") + ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1") { + artifact { + name = "com.ibm.mq.allclient" + extension = "jar" + } + isTransitive = false + } +} + +tasks.shadowJar { + dependencies { + exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1")) + } +} + +val integrationTest = tasks.register("integrationTest") { + description = "Runs integration tests." + group = "verification" + + testClassesDirs = sourceSets["integrationTest"].output.classesDirs + classpath = sourceSets["integrationTest"].runtimeClasspath + shouldRunAfter("test") + + useJUnitPlatform() + + testLogging { + events("passed") + } +} diff --git a/ibm-mq-metrics/docs/metrics.md b/ibm-mq-metrics/docs/metrics.md new file mode 100644 index 000000000..63a71e3f2 --- /dev/null +++ b/ibm-mq-metrics/docs/metrics.md @@ -0,0 +1,801 @@ +# Produced Metrics + + +## Metric `ibm.mq.message.retry.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.retry.count` | Gauge | `{message}` | Number of message retries | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.retry.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.status` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.status` | Gauge | `1` | Channel status | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.status` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.max.sharing.conversations` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.max.sharing.conversations` | Gauge | `{conversation}` | Maximum number of conversations permitted on this channel instance. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.max.sharing.conversations` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.current.sharing.conversations` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.current.sharing.conversations` | Gauge | `{conversation}` | Current number of conversations permitted on this channel instance. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.current.sharing.conversations` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.byte.received` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.byte.received` | Gauge | `By` | Number of bytes received | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.byte.received` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.byte.sent` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.byte.sent` | Gauge | `By` | Number of bytes sent | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.byte.sent` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.buffers.received` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.buffers.received` | Gauge | `{buffer}` | Buffers received | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.buffers.received` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.buffers.sent` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.buffers.sent` | Gauge | `{buffer}` | Buffers sent | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.buffers.sent` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.message.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.count` | Gauge | `{message}` | Message count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.start.time` | int | The start time of the channel as seconds since Epoch. | `1748462702` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.job.name` | string | The job name | `0000074900000003` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.open.input.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.open.input.count` | Gauge | `{application}` | Count of applications sending messages to the queue | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.open.input.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [1] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[1] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.open.output.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.open.output.count` | Gauge | `{application}` | Count of applications consuming messages from the queue | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.open.output.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [2] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[2] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.high.queue.depth` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.high.queue.depth` | Gauge | `{percent}` | The current high queue depth | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.high.queue.depth` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [3] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[3] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.service.interval` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.service.interval` | Gauge | `{percent}` | The queue service interval | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.service.interval` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [4] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[4] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth.full.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth.full.event` | Counter | `{event}` | The number of full queue events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth.full.event` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [5] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[5] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth.high.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth.high.event` | Counter | `{event}` | The number of high queue events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth.high.event` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [6] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[6] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth.low.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth.low.event` | Counter | `{event}` | The number of low queue events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth.low.event` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [7] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[7] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.uncommitted.messages` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.uncommitted.messages` | Gauge | `{message}` | Number of uncommitted messages | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.uncommitted.messages` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [8] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[8] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.oldest.msg.age` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.oldest.msg.age` | Gauge | `microseconds` | Queue message oldest age | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.oldest.msg.age` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [9] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[9] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.current.max.queue.filesize` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.current.max.queue.filesize` | Gauge | `By` | Current maximum queue file size | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.current.max.queue.filesize` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [10] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[10] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.current.queue.filesize` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.current.queue.filesize` | Gauge | `By` | Current queue file size | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.current.queue.filesize` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [11] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[11] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.instances.per.client` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.instances.per.client` | Gauge | `{instance}` | Instances per client | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.instances.per.client` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [12] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[12] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.message.deq.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.deq.count` | Gauge | `{message}` | Message dequeue count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.deq.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [13] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[13] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.message.enq.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.enq.count` | Gauge | `{message}` | Message enqueue count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.enq.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [14] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[14] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.queue.depth` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.queue.depth` | Gauge | `{message}` | Current queue depth | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.queue.depth` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [15] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[15] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.service.interval.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.service.interval.event` | Gauge | `1` | Queue service interval event | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.service.interval.event` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [16] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[16] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.reusable.log.size` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.reusable.log.size` | Gauge | `By` | The amount of space occupied, in megabytes, by log extents available to be reused. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.reusable.log.size` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.manager.active.channels` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.active.channels` | Gauge | `{channel}` | The queue manager active maximum channels limit | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.active.channels` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.restart.log.size` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.restart.log.size` | Gauge | `By` | Size of the log data required for restart recovery in megabytes. | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.restart.log.size` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.max.queue.depth` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.max.queue.depth` | Gauge | `{message}` | Maximum queue depth | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.max.queue.depth` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [17] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[17] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.onqtime.short_period` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.onqtime.short_period` | Gauge | `microseconds` | Amount of time, in microseconds, that a message spent on the queue, over a short period | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.onqtime.short_period` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [18] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[18] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.onqtime.long_period` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.onqtime.long_period` | Gauge | `microseconds` | Amount of time, in microseconds, that a message spent on the queue, over a longer period | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.onqtime.long_period` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.type` | string | The queue type | `local-normal` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [19] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[19] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.message.received.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.received.count` | Gauge | `{message}` | Number of messages received | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.received.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.message.sent.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.message.sent.count` | Gauge | `{message}` | Number of messages sent | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.message.sent.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.max.instances` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.max.instances` | Gauge | `{instance}` | Max channel instances | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.max.instances` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.channel.name` | string | The name of the channel | `DEV.ADMIN.SVRCONN` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.channel.type` | string | The type of the channel | `server-connection`; `cluster-receiver`; `amqp` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.connection.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.connection.count` | Gauge | `{connection}` | Active connections count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.connection.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.manager.status` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.status` | Gauge | `1` | Queue manager status | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.status` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.heartbeat` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.heartbeat` | Gauge | `1` | Queue manager heartbeat | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.heartbeat` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.archive.log.size` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.archive.log.size` | Gauge | `By` | Queue manager archive log size | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.archive.log.size` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.manager.max.active.channels` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.max.active.channels` | Gauge | `{channel}` | Queue manager max active channels | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.max.active.channels` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.manager.statistics.interval` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.statistics.interval` | Gauge | `1` | Queue manager statistics interval | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.statistics.interval` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.publish.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.publish.count` | Gauge | `{publication}` | Topic publication count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.publish.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [20] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[20] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.subscription.count` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.subscription.count` | Gauge | `{subscription}` | Topic subscription count | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.subscription.count` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `messaging.destination.name` | string | The system-specific name of the messaging operation. [21] | `dev/` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[21] `messaging.destination.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.listener.status` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.listener.status` | Gauge | `1` | Listener status | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.listener.status` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.listener.name` | string | The listener name | `listener` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.unauthorized.event` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.unauthorized.event` | Counter | `{event}` | Number of authentication error events | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.unauthorized.event` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `service.name` | string | Logical name of the service. [22] | `Wordle`; `JMSService` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `user.name` | string | Short name or login/username of the user. [23] | `foo`; `root` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + +**[22] `service.name`:** This is duplicated from otel semantic-conventions. + +**[23] `user.name`:** This is duplicated from otel semantic-conventions. + + + +## Metric `ibm.mq.manager.max.handles` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.manager.max.handles` | Gauge | `{event}` | Max open handles | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.manager.max.handles` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | diff --git a/ibm-mq-metrics/model/attributes.yaml b/ibm-mq-metrics/model/attributes.yaml new file mode 100644 index 000000000..ef7ffdafd --- /dev/null +++ b/ibm-mq-metrics/model/attributes.yaml @@ -0,0 +1,75 @@ +groups: + - id: shared.attributes + type: attribute_group + brief: Attributes of metrics. + attributes: + - id: ibm.mq.queue.manager + type: string + brief: > + The name of the IBM queue manager + stability: development + examples: ["MQ1"] + - id: messaging.destination.name + type: string + brief: > + The system-specific name of the messaging operation. + note: This is duplicated from otel semantic-conventions. + stability: development + examples: [ "dev/" ] + - id: ibm.mq.channel.name + type: string + brief: > + The name of the channel + stability: development + examples: [ "DEV.ADMIN.SVRCONN" ] + - id: ibm.mq.channel.type + type: string + brief: > + The type of the channel + stability: development + examples: [ "server-connection", "cluster-receiver", "amqp" ] + - id: ibm.mq.job.name + type: string + brief: > + The job name + stability: development + examples: [ "0000074900000003" ] + - id: ibm.mq.channel.start.time + type: int + brief: > + The start time of the channel as seconds since Epoch. + stability: development + examples: [ 1748462702 ] + # Use the messaging.destination.name attribute instead? +# - id: queue.name +# type: string +# brief: > +# The queue name +# stability: development +# examples: [ "DEV.DEAD.LETTER.QUEUE" ] + - id: ibm.mq.queue.type + type: string + brief: > + The queue type + stability: development + examples: [ "local-normal" ] + - id: ibm.mq.listener.name + type: string + brief: > + The listener name + stability: development + examples: [ "listener" ] + - id: user.name + type: string + brief: > + Short name or login/username of the user. + note: This is duplicated from otel semantic-conventions. + stability: development + examples: [ "foo", "root" ] + - id: service.name + type: string + brief: > + Logical name of the service. + note: This is duplicated from otel semantic-conventions. + stability: development + examples: [ "Wordle", "JMSService" ] diff --git a/ibm-mq-metrics/model/metrics.yaml b/ibm-mq-metrics/model/metrics.yaml new file mode 100644 index 000000000..cdc20456a --- /dev/null +++ b/ibm-mq-metrics/model/metrics.yaml @@ -0,0 +1,611 @@ +groups: + - id: ibm.mq.message.retry.count + type: metric + metric_name: ibm.mq.message.retry.count + stability: development + brief: "Number of message retries" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.status + type: metric + metric_name: ibm.mq.status + stability: development + brief: "Channel status" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.max.sharing.conversations + type: metric + metric_name: ibm.mq.max.sharing.conversations + stability: development + brief: "Maximum number of conversations permitted on this channel instance." + instrument: gauge + unit: "{conversation}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.current.sharing.conversations + type: metric + metric_name: ibm.mq.current.sharing.conversations + stability: development + unit: "{conversation}" + brief: "Current number of conversations permitted on this channel instance." + instrument: gauge + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.byte.received + type: metric + metric_name: ibm.mq.byte.received + stability: development + brief: "Number of bytes received" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.byte.sent + type: metric + metric_name: ibm.mq.byte.sent + stability: development + brief: "Number of bytes sent" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.buffers.received + type: metric + metric_name: ibm.mq.buffers.received + stability: development + brief: "Buffers received" + instrument: gauge + unit: "{buffer}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.buffers.sent + type: metric + metric_name: ibm.mq.buffers.sent + stability: development + brief: "Buffers sent" + unit: "{buffer}" + instrument: gauge + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.message.count + type: metric + metric_name: ibm.mq.message.count + stability: development + brief: "Message count" + unit: "{message}" + instrument: gauge + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.job.name + requirement_level: required + - ref: ibm.mq.channel.start.time + requirement_level: required + - id: ibm.mq.open.input.count + type: metric + metric_name: ibm.mq.open.input.count + stability: development + brief: "Count of applications sending messages to the queue" + instrument: gauge + unit: "{application}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.open.output.count + type: metric + metric_name: ibm.mq.open.output.count + stability: development + brief: "Count of applications consuming messages from the queue" + instrument: gauge + unit: "{application}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.high.queue.depth + type: metric + metric_name: ibm.mq.high.queue.depth + stability: development + brief: "The current high queue depth" + instrument: gauge + unit: "{percent}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.service.interval + type: metric + metric_name: ibm.mq.service.interval + stability: development + brief: "The queue service interval" + instrument: gauge + unit: "{percent}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.queue.depth.full.event + type: metric + metric_name: ibm.mq.queue.depth.full.event + stability: development + brief: "The number of full queue events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.queue.depth.high.event + type: metric + metric_name: ibm.mq.queue.depth.high.event + stability: development + brief: "The number of high queue events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.queue.depth.low.event + type: metric + metric_name: ibm.mq.queue.depth.low.event + stability: development + brief: "The number of low queue events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.uncommitted.messages + type: metric + metric_name: ibm.mq.uncommitted.messages + stability: development + brief: "Number of uncommitted messages" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.oldest.msg.age + type: metric + metric_name: ibm.mq.oldest.msg.age + stability: development + brief: "Queue message oldest age" + instrument: gauge + unit: "microseconds" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.current.max.queue.filesize + type: metric + metric_name: ibm.mq.current.max.queue.filesize + stability: development + brief: "Current maximum queue file size" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.current.queue.filesize + type: metric + metric_name: ibm.mq.current.queue.filesize + stability: development + brief: "Current queue file size" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.instances.per.client + type: metric + metric_name: ibm.mq.instances.per.client + stability: development + brief: "Instances per client" + instrument: gauge + unit: "{instance}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.message.deq.count + type: metric + metric_name: ibm.mq.message.deq.count + stability: development + brief: "Message dequeue count" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.message.enq.count + type: metric + metric_name: ibm.mq.message.enq.count + stability: development + brief: "Message enqueue count" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.queue.depth + type: metric + metric_name: ibm.mq.queue.depth + stability: development + brief: "Current queue depth" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.service.interval.event + type: metric + metric_name: ibm.mq.service.interval.event + stability: development + brief: "Queue service interval event" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.reusable.log.size + type: metric + metric_name: ibm.mq.reusable.log.size + stability: development + brief: "The amount of space occupied, in megabytes, by log extents available to be reused." + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.active.channels + type: metric + metric_name: ibm.mq.manager.active.channels + stability: development + brief: "The queue manager active maximum channels limit" + instrument: gauge + unit: "{channel}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.restart.log.size + type: metric + metric_name: ibm.mq.restart.log.size + stability: development + brief: "Size of the log data required for restart recovery in megabytes." + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.max.queue.depth + type: metric + metric_name: ibm.mq.max.queue.depth + stability: development + brief: "Maximum queue depth" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.onqtime.short_period + type: metric + metric_name: ibm.mq.onqtime.short_period + stability: development + brief: "Amount of time, in microseconds, that a message spent on the queue, over a short period" + instrument: gauge + unit: "microseconds" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.onqtime.long_period + type: metric + metric_name: ibm.mq.onqtime.long_period + stability: development + brief: "Amount of time, in microseconds, that a message spent on the queue, over a longer period" + instrument: gauge + unit: "microseconds" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - ref: ibm.mq.queue.type + requirement_level: required + - id: ibm.mq.message.received.count + type: metric + metric_name: ibm.mq.message.received.count + stability: development + brief: "Number of messages received" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.message.sent.count + type: metric + metric_name: ibm.mq.message.sent.count + stability: development + brief: "Number of messages sent" + instrument: gauge + unit: "{message}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.max.instances + type: metric + metric_name: ibm.mq.max.instances + stability: development + brief: "Max channel instances" + instrument: gauge + unit: "{instance}" + attributes: + - ref: ibm.mq.channel.name + requirement_level: required + - ref: ibm.mq.channel.type + requirement_level: required + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.connection.count + type: metric + metric_name: ibm.mq.connection.count + stability: development + brief: "Active connections count" + instrument: gauge + unit: "{connection}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.status + type: metric + metric_name: ibm.mq.manager.status + stability: development + brief: "Queue manager status" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.heartbeat + type: metric + metric_name: ibm.mq.heartbeat + stability: development + brief: "Queue manager heartbeat" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.archive.log.size + type: metric + metric_name: ibm.mq.archive.log.size + stability: development + brief: "Queue manager archive log size" + instrument: gauge + unit: "By" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.max.active.channels + type: metric + metric_name: ibm.mq.manager.max.active.channels + stability: development + brief: "Queue manager max active channels" + instrument: gauge + unit: "{channel}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.manager.statistics.interval + type: metric + metric_name: ibm.mq.manager.statistics.interval + stability: development + brief: "Queue manager statistics interval" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - id: ibm.mq.publish.count + type: metric + metric_name: ibm.mq.publish.count + stability: development + brief: "Topic publication count" + instrument: gauge + unit: "{publication}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.subscription.count + type: metric + metric_name: ibm.mq.subscription.count + stability: development + brief: "Topic subscription count" + instrument: gauge + unit: "{subscription}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: messaging.destination.name + requirement_level: required + - id: ibm.mq.listener.status + type: metric + metric_name: ibm.mq.listener.status + stability: development + brief: "Listener status" + instrument: gauge + unit: "1" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: ibm.mq.listener.name + requirement_level: required + - id: ibm.mq.unauthorized.event + type: metric + metric_name: ibm.mq.unauthorized.event + stability: development + brief: "Number of authentication error events" + instrument: counter + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: user.name + requirement_level: required + - ref: service.name + requirement_level: required + - id: ibm.mq.manager.max.handles + type: metric + metric_name: ibm.mq.manager.max.handles + stability: development + brief: "Max open handles" + instrument: gauge + unit: "{event}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required diff --git a/ibm-mq-metrics/model/registry_manifest.yaml b/ibm-mq-metrics/model/registry_manifest.yaml new file mode 100644 index 000000000..1fb92e0cb --- /dev/null +++ b/ibm-mq-metrics/model/registry_manifest.yaml @@ -0,0 +1,3 @@ +name: IBM MQ Monitoring +semconv_version: v1.34.0 +schema_base_url: https://www.github.com/open-telemetry/opentelemetry-java-contrib/ibm-mq-metrics diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java new file mode 100644 index 000000000..ef40efa44 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/JakartaPutGet.java @@ -0,0 +1,293 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.integration.tests; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import com.ibm.msg.client.jakarta.jms.JmsConnectionFactory; +import com.ibm.msg.client.jakarta.jms.JmsFactoryFactory; +import com.ibm.msg.client.jakarta.wmq.WMQConstants; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.util.WmqUtil; +import jakarta.jms.Destination; +import jakarta.jms.JMSConsumer; +import jakarta.jms.JMSContext; +import jakarta.jms.JMSException; +import jakarta.jms.JMSProducer; +import jakarta.jms.JMSRuntimeException; +import jakarta.jms.TextMessage; + +/** + * This code was adapted from https://github.com/ibm-messaging/mq-dev-samples/. + * + *

A minimal and simple application for Point-to-point messaging. + * + *

Application makes use of fixed literals, any customisations will require re-compilation of + * this source file. Application assumes that the named queue is empty prior to a run. + * + *

Notes: + * + *

API type: Jakarta API (JMS v3.0, simplified domain) + * + *

Messaging domain: Point-to-point + * + *

Provider type: IBM MQ + * + *

Connection mode: Client connection + * + *

JNDI in use: No + */ +public final class JakartaPutGet { + + private JakartaPutGet() {} + + public static void createQueue(QueueManager manager, String name, int maxDepth) { + MQQueueManager ibmQueueManager = WmqUtil.connectToQueueManager(manager); + PCFMessageAgent agent = WmqUtil.initPcfMessageAgent(manager, ibmQueueManager); + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_CREATE_Q); + request.addParameter(com.ibm.mq.constants.CMQC.MQCA_Q_NAME, name); + request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_LOCAL); + + request.addParameter(CMQC.MQIA_MAX_Q_DEPTH, maxDepth); + // these parameters are indicated in percentage of max depth. + request.addParameter(CMQC.MQIA_Q_DEPTH_HIGH_LIMIT, 75); + request.addParameter(CMQC.MQIA_Q_DEPTH_LOW_LIMIT, 20); + request.addParameter(CMQC.MQIA_Q_DEPTH_HIGH_EVENT, CMQCFC.MQEVR_ENABLED); + request.addParameter(CMQC.MQIA_Q_DEPTH_LOW_EVENT, CMQCFC.MQEVR_ENABLED); + request.addParameter(CMQC.MQIA_Q_DEPTH_MAX_EVENT, CMQCFC.MQEVR_ENABLED); + try { + agent.send(request); + } catch (PCFException e) { + if (e.reasonCode == CMQCFC.MQRCCF_OBJECT_ALREADY_EXISTS) { + return; + } + throw new RuntimeException(e); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * @param manager Queue manager configuration + * @param queueName Queue that the application uses to put and get messages to and from + * @param numberOfMessages Number of messages to send + * @param sleepIntervalMs Sleep interval in ms + */ + public static void runPutGet( + QueueManager manager, String queueName, int numberOfMessages, int sleepIntervalMs) { + + createQueue(manager, queueName, 100000); + JMSContext context = null; + JMSContext senderContext = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "JakartaPutGet (Jakarta)"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, manager.getPassword()); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + Destination destination = context.createQueue("queue:///" + queueName); + + JMSConsumer consumer = context.createConsumer(destination); + consumer.setMessageListener(message -> {}); + + senderContext = cf.createContext(); + Destination senderDestination = senderContext.createQueue("queue:///" + queueName); + + for (int i = 0; i < numberOfMessages; i++) { + long uniqueNumber = System.currentTimeMillis() % 1000; + TextMessage message = + senderContext.createTextMessage("Your lucky number today is " + uniqueNumber); + message.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, 37); + JMSProducer producer = senderContext.createProducer(); + producer.send(senderDestination, message); + + Thread.sleep(sleepIntervalMs); + } + + } catch (JMSException | InterruptedException jmsex) { + throw new RuntimeException(jmsex); + } finally { + if (context != null) { + context.close(); + } + if (senderContext != null) { + senderContext.close(); + } + } + } + + /** + * Send a number of messages to the queue. + * + * @param manager Queue manager configuration + * @param queueName Queue that the application uses to put and get messages to and from + * @param numberOfMessages Number of messages to send + */ + public static void sendMessages(QueueManager manager, String queueName, int numberOfMessages) { + + createQueue(manager, queueName, 1000); + JMSContext context = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Message Sender"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, manager.getPassword()); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + Destination destination = context.createQueue("queue:///" + queueName); + + for (int i = 0; i < numberOfMessages; i++) { + long uniqueNumber = System.currentTimeMillis() % 1000; + TextMessage message = + context.createTextMessage("Your lucky number today is " + uniqueNumber); + message.setIntProperty(WMQConstants.JMS_IBM_CHARACTER_SET, 37); + JMSProducer producer = context.createProducer(); + producer.send(destination, message); + } + + } catch (JMSException e) { + throw new RuntimeException(e); + } catch (JMSRuntimeException e) { + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + if (mqe.getReason() == 2053) { // queue is full + return; + } + } + throw new RuntimeException(e); + } finally { + if (context != null) { + context.close(); + } + } + } + + /** + * Reads all the messages of the queue. + * + * @param manager Queue manager configuration + * @param queueName Queue that the application uses to put and get messages to and from + */ + public static void readMessages(QueueManager manager, String queueName) { + JMSContext context = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Message Receiver"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, manager.getPassword()); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + Destination destination = context.createQueue("queue:///" + queueName); + + JMSConsumer consumer = context.createConsumer(destination); // autoclosable + while (consumer.receiveBody(String.class, 100) != null) {} + + } catch (JMSException e) { + throw new RuntimeException(e); + } catch (JMSRuntimeException e) { + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + if (mqe.getReason() == CMQC.MQRC_NO_MSG_AVAILABLE) { // out of messages, we read them all. + return; + } + } + throw new RuntimeException(e); + } finally { + if (context != null) { + context.close(); + } + } + } + + public static void tryLoginWithBadPassword(QueueManager manager) { + + JMSContext context = null; + try { + // Create a connection factory + JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.JAKARTA_WMQ_PROVIDER); + JmsConnectionFactory cf = ff.createConnectionFactory(); + + // Set the properties + cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, manager.getHost()); + cf.setIntProperty(WMQConstants.WMQ_PORT, manager.getPort()); + cf.setStringProperty(WMQConstants.WMQ_CHANNEL, manager.getChannelName()); + cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT); + cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, manager.getName()); + cf.setStringProperty(WMQConstants.WMQ_APPLICATIONNAME, "Bad Password"); + cf.setBooleanProperty(WMQConstants.USER_AUTHENTICATION_MQCSP, true); + cf.setStringProperty(WMQConstants.USERID, manager.getUsername()); + cf.setStringProperty(WMQConstants.PASSWORD, "badpassword"); + // cf.setStringProperty(WMQConstants.WMQ_SSL_CIPHER_SUITE, "*TLS12ORHIGHER"); + // cf.setIntProperty(MQConstants.CERTIFICATE_VALIDATION_POLICY, + // MQConstants.MQ_CERT_VAL_POLICY_NONE); + + // Create Jakarta objects + context = cf.createContext(); + } catch (JMSException e) { + throw new RuntimeException(e); + } catch (JMSRuntimeException e) { + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + if (mqe.getReason() == 2035) { // bad password + return; + } + } + throw new RuntimeException(e); + } finally { + if (context != null) { + context.close(); + } + } + } +} diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java new file mode 100644 index 000000000..8221c9353 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/TestWMQMonitor.java @@ -0,0 +1,58 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.integration.tests; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.fasterxml.jackson.databind.ObjectMapper; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.WmqMonitor; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ExecutorService; + +/** + * The TestWMQMonitor class extends the WMQMonitor class and provides a test implementation of the + * WebSphere MQ monitoring functionality. It is intended for internal integration test purposes and + * facilitates custom configuration through a test configuration file and a test metric write + * helper. + */ +class TestWMQMonitor { + + private final ConfigWrapper config; + private final ExecutorService threadPool; + private final Meter meter; + + TestWMQMonitor(ConfigWrapper config, Meter meter, ExecutorService service) { + this.config = config; + this.threadPool = service; + this.meter = meter; + } + + /** + * Executes a test run for monitoring WebSphere MQ queue managers based on the provided + * configuration "testConfigFile". + * + *

The method retrieves "queueManagers" from the yml configuration file and uses a custom + * MetricWriteHelper if provided, initializes a TasksExecutionServiceProvider, and executes the + * WMQMonitorTask + */ + void runTest() { + List> queueManagers = config.getQueueManagers(); + assertThat(queueManagers).isNotNull(); + ObjectMapper mapper = new ObjectMapper(); + + WmqMonitor wmqTask = new WmqMonitor(config, threadPool, meter); + + // we override this helper to pass in our opentelemetry helper instead. + for (Map queueManager : queueManagers) { + QueueManager qManager = mapper.convertValue(queueManager, QueueManager.class); + wmqTask.run(qManager); + } + } +} diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java new file mode 100644 index 000000000..9f0a64f21 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java @@ -0,0 +1,284 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.integration.tests; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.ibm.mq.opentelemetry.Main; +import io.opentelemetry.ibm.mq.util.WmqUtil; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.io.File; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Integration Test for WMQMonitor */ +@Disabled +class WMQMonitorIntegrationTest { + + private static final Logger logger = LoggerFactory.getLogger(WMQMonitorIntegrationTest.class); + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + private static final ExecutorService service = + Executors.newFixedThreadPool( + 4, /* one gets burned with our @BeforeAll message uzi, 4 is faster than 2 */ + r -> { + Thread thread = new Thread(r); + thread.setUncaughtExceptionHandler( + (t, e) -> { + logger.error("Uncaught exception", e); + fail(e.getMessage()); + }); + thread.setDaemon(true); + thread.setName("WMQMonitorIntegrationTest"); + return thread; + }); + + private static QueueManager getQueueManagerConfig() throws Exception { + String configFile = getConfigFile("conf/test-config.yml"); + ConfigWrapper wrapper = ConfigWrapper.parse(configFile); + Map queueManagerConfig = wrapper.getQueueManagers().get(0); + ObjectMapper mapper = new ObjectMapper(); + return mapper.convertValue(queueManagerConfig, QueueManager.class); + } + + @NotNull + private static String getConfigFile(String resourcePath) throws URISyntaxException { + URL resource = WMQMonitorIntegrationTest.class.getClassLoader().getResource(resourcePath); + if (resource == null) { + throw new IllegalArgumentException("file not found!"); + } + + File file = Paths.get(resource.toURI()).toFile(); + logger.info("Config file: {}", file.getAbsolutePath()); + return file.getAbsolutePath(); + } + + private static void configureQueueManager(QueueManager manager) { + MQQueueManager ibmQueueManager = WmqUtil.connectToQueueManager(manager); + PCFMessageAgent agent = WmqUtil.initPcfMessageAgent(manager, ibmQueueManager); + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_CHANGE_Q_MGR); + // turn on emitting authority events + request.addParameter(CMQC.MQIA_AUTHORITY_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting configuration events + request.addParameter(CMQC.MQIA_CONFIGURATION_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting channel auto-definition events + request.addParameter(CMQC.MQIA_CHANNEL_AUTO_DEF_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting channel events + request.addParameter(CMQC.MQIA_CHANNEL_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting command events + request.addParameter(CMQC.MQIA_COMMAND_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting inhibit events + request.addParameter(CMQC.MQIA_INHIBIT_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting local events + request.addParameter(CMQC.MQIA_LOCAL_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting performance events + request.addParameter(CMQC.MQIA_PERFORMANCE_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting remote events + request.addParameter(CMQC.MQIA_REMOTE_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting SSL events + request.addParameter(CMQC.MQIA_SSL_EVENT, CMQCFC.MQEVR_ENABLED); + // turn on emitting start/stop events + request.addParameter(CMQC.MQIA_START_STOP_EVENT, CMQCFC.MQEVR_ENABLED); + try { + agent.send(request); + } catch (Exception e) { + if (e instanceof PCFException) { + PCFMessage[] msgs = (PCFMessage[]) ((PCFException) e).exceptionSource; + for (PCFMessage msg : msgs) { + logger.error(msg.toString()); + } + } + throw new RuntimeException(e); + } + } + + @BeforeAll + public static void sendClientMessages() throws Exception { + QueueManager qManager = getQueueManagerConfig(); + configureQueueManager(qManager); + + // create a queue and fill it up past its capacity. + JakartaPutGet.createQueue(qManager, "smallqueue", 10); + + JakartaPutGet.runPutGet(qManager, "myqueue", 10, 1); + + Future ignored = + service.submit(() -> JakartaPutGet.runPutGet(qManager, "myqueue", 1000000, 100)); + } + + @AfterAll + public static void stopSendingClientMessages() throws Exception { + QueueManager qManager = getQueueManagerConfig(); + configureQueueManager(qManager); + + service.shutdown(); + } + + @BeforeEach + void setUpEvents() throws Exception { + QueueManager qManager = getQueueManagerConfig(); + // try to login with a bad password: + JakartaPutGet.tryLoginWithBadPassword(qManager); + + JakartaPutGet.sendMessages(qManager, "smallqueue", 1); + Thread.sleep(1000); + JakartaPutGet.sendMessages(qManager, "smallqueue", 8); + Thread.sleep(1000); + JakartaPutGet.sendMessages(qManager, "smallqueue", 5); + } + + @AfterEach + void clearQueue() throws Exception { + // clear the full queue. + JakartaPutGet.readMessages(getQueueManagerConfig(), "smallqueue"); + } + + @Test + void test_monitor_with_full_config() throws Exception { + String configFile = getConfigFile("conf/test-config.yml"); + + ConfigWrapper config = ConfigWrapper.parse(configFile); + Meter meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + TestWMQMonitor monitor = new TestWMQMonitor(config, meter, service); + monitor.runTest(); + + List data = otelTesting.getMetrics(); + Map metrics = new HashMap<>(); + for (MetricData metricData : data) { + metrics.put(metricData.getName(), metricData); + } + Set metricNames = metrics.keySet(); + // this value is read from the active channels count: + assertThat(metricNames).contains("ibm.mq.manager.active.channels"); + // this value is read from the configuration queue. + assertThat(metricNames).contains("ibm.mq.manager.max.handles"); + // this value is read from the queue manager events, for unauthorized events. + assertThat(metricNames).contains("ibm.mq.unauthorized.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.full.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.high.event"); + assertThat(metricNames).contains("ibm.mq.queue.depth.low.event"); + // reads a value from the heartbeat gauge + assertThat(metricNames).contains("ibm.mq.heartbeat"); + assertThat(metricNames).contains("ibm.mq.oldest.msg.age"); + if (metrics.get("ibm.mq.oldest.msg.age") != null) { + Set queueNames = + metrics.get("ibm.mq.oldest.msg.age").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(MESSAGING_DESTINATION_NAME)) + .collect(Collectors.toSet()); + assertThat(queueNames).contains("smallqueue"); + } + // make sure we get MQ manager status + assertThat(metricNames).contains("ibm.mq.manager.status"); + if (metrics.get("ibm.mq.manager.status") != null) { + Set queueManagers = + metrics.get("ibm.mq.manager.status").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(IBM_MQ_QUEUE_MANAGER)) + .collect(Collectors.toSet()); + assertThat(queueManagers).contains("QM1"); + } + + assertThat(metricNames).contains("ibm.mq.onqtime.2"); + if (metrics.get("ibm.mq.onqtime.2") != null) { + Set queueNames = + metrics.get("ibm.mq.onqtime.2").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(MESSAGING_DESTINATION_NAME)) + .collect(Collectors.toSet()); + assertThat(queueNames).contains("smallqueue"); + Set queueManagers = + metrics.get("ibm.mq.manager.status").getLongGaugeData().getPoints().stream() + .map(pt -> pt.getAttributes().get(IBM_MQ_QUEUE_MANAGER)) + .collect(Collectors.toSet()); + assertThat(queueManagers).contains("QM1"); + // TODO: Add more asserts about data values, units, attributes, etc, not just names + } + } + + @Test + void test_wmqmonitor() throws Exception { + String configFile = getConfigFile("conf/test-queuemgr-config.yml"); + ConfigWrapper config = ConfigWrapper.parse(configFile); + Meter meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + + TestWMQMonitor monitor = new TestWMQMonitor(config, meter, service); + monitor.runTest(); + // TODO: Wait why are there no asserts here? + } + + @Test + void test_otlphttp() throws Exception { + ConfigWrapper config = + ConfigWrapper.parse(WMQMonitorIntegrationTest.getConfigFile("conf/test-config.yml")); + ScheduledExecutorService service = + Executors.newScheduledThreadPool(config.getNumberOfThreads()); + Main.run(config, service, otelTesting.getOpenTelemetry()); + CountDownLatch latch = new CountDownLatch(1); + Future ignored = service.submit(latch::countDown); + Thread.sleep(5000); // TODO: This is fragile and time consuming and should be made better + service.shutdown(); + assertTrue(service.awaitTermination(30, TimeUnit.SECONDS)); + + List data = otelTesting.getMetrics(); + Set metricNames = new HashSet<>(); + for (MetricData metricData : data) { + metricNames.add(metricData.getName()); + } + // this value is read from the active channels count: + assertThat(metricNames).contains("ibm.mq.manager.active.channels"); + // this value is read from the configuration queue. + assertThat(metricNames).contains("ibm.mq.manager.max.handles"); + // this value is read from the queue manager events, for unauthorized events. + assertThat(metricNames).contains("ibm.mq.unauthorized.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.full.event"); + // this value is read from the performance event queue. + assertThat(metricNames).contains("ibm.mq.queue.depth.high.event"); + assertThat(metricNames).contains("ibm.mq.queue.depth.low.event"); + // reads a value from the heartbeat gauge + assertThat(metricNames).contains("ibm.mq.heartbeat"); + } +} diff --git a/ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml b/ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml new file mode 100644 index 000000000..0093bc9e7 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/resources/conf/test-config.yml @@ -0,0 +1,197 @@ + +#This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. +#No need to change the default unless you know what you are doing. +#queueMetricsCollectionTimeoutInSeconds: 40 +#channelMetricsCollectionTimeoutInSeconds: 40 +#topicMetricsCollectionTimeoutInSeconds: 40 + +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Client" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + channelName: DEV.ADMIN.SVRCONN + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + username: "admin" + password: "passw0rd" + + #PCF requests are always sent to SYSTEM.ADMIN.COMMAND.QUEUE. The PCF responses to these requests are sent to the default reply-to queue called + #SYSTEM.DEFAULT.MODEL.QUEUE. However, you can override this behavior and send it to a temporary dynamic queue by changing the modelQueueName and replyQueuePrefix fields. + #For more details around this https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm & https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm + #modelQueueName: "" + #replyQueuePrefix: "" + + + #Sets the CCSID used in the message descriptor of request and response messages. The default value is MQC.MQCCSI_Q_MGR. + #To set this, please use the integer value. + #ccsid: + + #Sets the encoding used in the message descriptor of request and response messages. The default value is MQC.MQENC_NATIVE. + #To set this, please use the integer value. + #encoding: + + # IBM Cipher Suite e.g. "SSL_RSA_WITH_AES_128_CBC_SHA256".. + # For translation to IBM Cipher http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm + # A cipher working for IBM Cloud MQ and Temurin JDK 8 is TLS_AES_128_GCM_SHA256 + #cipherSuite: "TLS_AES_128_GCM_SHA256" + + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","$SYS"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: http://0.0.0.0:4318 + otel.exporter.otlp.protocol: http/protobuf + otel.metric.export.interval: 5s + otel.logs.exporter: none + otel.traces.exporter: none diff --git a/ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml b/ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml new file mode 100644 index 000000000..9d2808906 --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/resources/conf/test-queuemgr-config.yml @@ -0,0 +1,170 @@ +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Client" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + channelName: DEV.ADMIN.SVRCONN + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + username: "admin" + password: "passw0rd" + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","$SYS"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + trustStoreEncryptedPassword: "" + + keyStorePath: "" + keyStorePassword: "" + keyStoreEncryptedPassword: "" + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.metric.export.interval: 5s + otel.logs.exporter: none + otel.traces.exporter: none diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java new file mode 100644 index 000000000..6ded88547 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java @@ -0,0 +1,106 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq; + +import com.ibm.mq.constants.CMQC; +import io.opentelemetry.ibm.mq.config.QueueManager; +import java.util.Hashtable; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Takes care of websphere mq connection, authentication, SSL, Cipher spec, certificate based + * authorization.
+ * It also validates the arguments passed for various scenarios. + */ +public class WmqContext { + private static final String TRANSPORT_TYPE_CLIENT = "Client"; + private static final String TRANSPORT_TYPE_BINDINGS = "Bindings"; + + public static final Logger logger = LoggerFactory.getLogger(WmqContext.class); + private final QueueManager queueManager; + + public WmqContext(QueueManager queueManager) { + this.queueManager = queueManager; + validateArgs(); + } + + /** Note: This Hashtable type is needed for IBM client classes. */ + @SuppressWarnings("JdkObsolete") + public Hashtable getMqEnvironment() { + Hashtable env = new Hashtable<>(); + addEnvProperty(env, CMQC.HOST_NAME_PROPERTY, queueManager.getHost()); + addEnvProperty(env, CMQC.PORT_PROPERTY, queueManager.getPort()); + addEnvProperty(env, CMQC.CHANNEL_PROPERTY, queueManager.getChannelName()); + addEnvProperty(env, CMQC.USER_ID_PROPERTY, queueManager.getUsername()); + addEnvProperty(env, CMQC.PASSWORD_PROPERTY, queueManager.getPassword()); + addEnvProperty(env, CMQC.SSL_CERT_STORE_PROPERTY, queueManager.getSslKeyRepository()); + addEnvProperty(env, CMQC.SSL_CIPHER_SUITE_PROPERTY, queueManager.getCipherSuite()); + // TODO: investigate on CIPHER_SPEC property No Available in MQ 7.5 Jar + + if (TRANSPORT_TYPE_CLIENT.equalsIgnoreCase(queueManager.getTransportType())) { + addEnvProperty(env, CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_CLIENT); + } else if (TRANSPORT_TYPE_BINDINGS.equalsIgnoreCase(queueManager.getTransportType())) { + addEnvProperty(env, CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES_BINDINGS); + } else { + addEnvProperty(env, CMQC.TRANSPORT_PROPERTY, CMQC.TRANSPORT_MQSERIES); + } + + if (logger.isDebugEnabled()) { + logger.debug("Transport property is {}", env.get(CMQC.TRANSPORT_PROPERTY)); + } + return env; + } + + @SuppressWarnings({"unused", "unchecked", "rawtypes"}) + private static void addEnvProperty(Hashtable env, String propName, @Nullable Object propVal) { + if (null != propVal) { + if (propVal instanceof String) { + String propString = (String) propVal; + if (propString.isEmpty()) { + return; + } + } + env.put(propName, propVal); + } + } + + private void validateArgs() { + boolean validArgs = true; + StringBuilder errorMsg = new StringBuilder(); + if (queueManager == null) { + validArgs = false; + errorMsg.append("Queue manager cannot be null"); + } else { + if (TRANSPORT_TYPE_CLIENT.equalsIgnoreCase(queueManager.getTransportType())) { + if (queueManager.getHost() == null || queueManager.getHost().trim().isEmpty()) { + validArgs = false; + errorMsg.append("Host cannot be null or empty for client type connection. "); + } + if (queueManager.getPort() == -1) { + validArgs = false; + errorMsg.append("port should be set for client type connection. "); + } + if (queueManager.getChannelName() == null + || queueManager.getChannelName().trim().isEmpty()) { + validArgs = false; + errorMsg.append("Channel cannot be null or empty for client type connection. "); + } + } + if (TRANSPORT_TYPE_BINDINGS.equalsIgnoreCase(queueManager.getTransportType())) { + if (queueManager.getName() == null || queueManager.getName().trim().isEmpty()) { + validArgs = false; + errorMsg.append("queuemanager cannot be null or empty for bindings type connection. "); + } + } + } + + if (!validArgs) { + throw new IllegalArgumentException(errorMsg.toString()); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java new file mode 100644 index 000000000..7eaa2d8c0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java @@ -0,0 +1,209 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.metricscollector.ChannelMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.InquireChannelCmdCollector; +import io.opentelemetry.ibm.mq.metricscollector.InquireQueueManagerCmdCollector; +import io.opentelemetry.ibm.mq.metricscollector.ListenerMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.MetricsCollectorContext; +import io.opentelemetry.ibm.mq.metricscollector.PerformanceEventQueueCollector; +import io.opentelemetry.ibm.mq.metricscollector.QueueManagerEventCollector; +import io.opentelemetry.ibm.mq.metricscollector.QueueManagerMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.QueueMetricsCollector; +import io.opentelemetry.ibm.mq.metricscollector.ReadConfigurationEventQueueCollector; +import io.opentelemetry.ibm.mq.metricscollector.TopicMetricsCollector; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.ibm.mq.util.WmqUtil; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WmqMonitor { + + private static final Logger logger = LoggerFactory.getLogger(WmqMonitor.class); + + private final List queueManagers; + private final List> jobs = new ArrayList<>(); + private final LongGauge heartbeatGauge; + private final ExecutorService threadPool; + private final MetricsConfig metricsConfig; + + public WmqMonitor(ConfigWrapper config, ExecutorService threadPool, Meter meter) { + List> queueManagers = getQueueManagers(config); + ObjectMapper mapper = new ObjectMapper(); + + this.queueManagers = new ArrayList<>(); + + for (Map queueManager : queueManagers) { + try { + QueueManager qManager = mapper.convertValue(queueManager, QueueManager.class); + this.queueManagers.add(qManager); + } catch (Throwable t) { + logger.error("Error preparing queue manager {}", queueManager, t); + } + } + + this.metricsConfig = new MetricsConfig(config); + + this.heartbeatGauge = meter.gaugeBuilder("ibm.mq.heartbeat").setUnit("1").ofLongs().build(); + this.threadPool = threadPool; + + jobs.add(new QueueManagerMetricsCollector(meter)); + jobs.add(new InquireQueueManagerCmdCollector(meter)); + jobs.add(new ChannelMetricsCollector(meter)); + jobs.add(new InquireChannelCmdCollector(meter)); + jobs.add(new QueueMetricsCollector(meter, threadPool, config)); + jobs.add(new ListenerMetricsCollector(meter)); + jobs.add(new TopicMetricsCollector(meter)); + jobs.add(new ReadConfigurationEventQueueCollector(meter)); + jobs.add(new PerformanceEventQueueCollector(meter)); + jobs.add(new QueueManagerEventCollector(meter)); + } + + public void run() { + for (QueueManager qm : this.queueManagers) { + run(qm); + } + } + + public void run(QueueManager queueManager) { + String queueManagerName = queueManager.getName(); + logger.debug("WMQMonitor thread for queueManager {} started.", queueManagerName); + long startTime = System.currentTimeMillis(); + MQQueueManager ibmQueueManager = null; + PCFMessageAgent agent = null; + int heartBeatMetricValue = 0; + try { + ibmQueueManager = WmqUtil.connectToQueueManager(queueManager); + heartBeatMetricValue = 1; + agent = WmqUtil.initPcfMessageAgent(queueManager, ibmQueueManager); + extractAndReportMetrics(ibmQueueManager, queueManager, agent); + } catch (RuntimeException e) { + logger.error( + "Error connecting to QueueManager {} by thread {}: {}", + queueManagerName, + Thread.currentThread().getName(), + e.getMessage(), + e); + } finally { + if (this.metricsConfig.isIbmMqHeartbeatEnabled()) { + heartbeatGauge.set( + heartBeatMetricValue, Attributes.of(IBM_MQ_QUEUE_MANAGER, queueManagerName)); + } + cleanUp(ibmQueueManager, agent); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "WMQMonitor thread for queueManager {} ended. Time taken = {} ms", + queueManagerName, + endTime); + } + } + + @NotNull + private static List> getQueueManagers(ConfigWrapper config) { + List> queueManagers = config.getQueueManagers(); + if (queueManagers.isEmpty()) { + throw new IllegalStateException( + "The 'queueManagers' section in config.yml is empty or otherwise incorrect."); + } + return queueManagers; + } + + private void extractAndReportMetrics( + MQQueueManager mqQueueManager, QueueManager queueManager, PCFMessageAgent agent) { + logger.debug("Queueing {} jobs", jobs.size()); + MetricsCollectorContext context = + new MetricsCollectorContext(queueManager, agent, mqQueueManager, this.metricsConfig); + List> tasks = new ArrayList<>(); + for (Consumer collector : jobs) { + tasks.add( + () -> { + try { + long startTime = System.currentTimeMillis(); + collector.accept(context); + long diffTime = System.currentTimeMillis() - startTime; + if (diffTime > 60000L) { + logger.warn( + "{} Task took {} ms to complete", + collector.getClass().getSimpleName(), + diffTime); + } else { + logger.debug( + "{} Task took {} ms to complete", + collector.getClass().getSimpleName(), + diffTime); + } + } catch (RuntimeException e) { + logger.error( + "Error while running task name = {}", collector.getClass().getSimpleName(), e); + } + return null; + }); + } + + try { + this.threadPool.invokeAll(tasks); + } catch (InterruptedException e) { + logger.error("Error while the thread {} is waiting ", Thread.currentThread().getName(), e); + } + } + + /** Destroy the agent and disconnect from queue manager */ + private static void cleanUp( + @Nullable MQQueueManager ibmQueueManager, @Nullable PCFMessageAgent agent) { + // Disconnect the agent. + + if (agent != null) { + String qMgrName = agent.getQManagerName(); + try { + agent.disconnect(); + logger.debug( + "PCFMessageAgent disconnected for queueManager {} in thread {}", + qMgrName, + Thread.currentThread().getName()); + } catch (Exception e) { + logger.error( + "Error occurred while disconnecting PCFMessageAgent for queueManager {} in thread {}", + qMgrName, + Thread.currentThread().getName(), + e); + } + } + + // Disconnect queue manager + if (ibmQueueManager != null) { + String name = ""; + try { + name = ibmQueueManager.getName(); + ibmQueueManager.disconnect(); + } catch (Exception e) { + logger.error( + "Error occurred while disconnecting queueManager {} in thread {}", + name, + Thread.currentThread().getName(), + e); + } + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java new file mode 100644 index 000000000..f02f05cc0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java @@ -0,0 +1,85 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.config; + +import io.opentelemetry.ibm.mq.metricscollector.FilterType; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** A jackson databind class used for config. */ +public class ExcludeFilters { + + private String type = "UNKNOWN"; + private Set values = new HashSet<>(); + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public Set getValues() { + return Collections.unmodifiableSet(values); + } + + public void setValues(Set values) { + this.values = new HashSet<>(values); + } + + public static boolean isExcluded(String resourceName, Collection excludeFilters) { + if (excludeFilters == null) { + return false; + } + for (ExcludeFilters filter : excludeFilters) { + if (filter.isExcluded(resourceName)) { + return true; + } + } + return false; + } + + public boolean isExcluded(String resourceName) { + if (resourceName == null || resourceName.isEmpty()) { + return true; + } + switch (FilterType.valueOf(type)) { + case CONTAINS: + for (String filterValue : values) { + if (resourceName.contains(filterValue)) { + return true; + } + } + break; + case STARTSWITH: + for (String filterValue : values) { + if (resourceName.startsWith(filterValue)) { + return true; + } + } + break; + case NONE: + return false; + case EQUALS: + for (String filterValue : values) { + if (resourceName.equals(filterValue)) { + return true; + } + } + break; + case ENDSWITH: + for (String filterValue : values) { + if (resourceName.endsWith(filterValue)) { + return true; + } + } + } + return false; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java new file mode 100644 index 000000000..685840977 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java @@ -0,0 +1,257 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.config; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import javax.annotation.Nullable; + +/** This is a jackson databind class used purely for config. */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class QueueManager { + + @Nullable private String host; + private int port = -1; + private String name = "UNKNOWN"; + @Nullable private String channelName; + @Nullable private String transportType; + @Nullable private String username; + @Nullable private String password; + @Nullable private String sslKeyRepository; + private int ccsid = Integer.MIN_VALUE; + private int encoding = Integer.MIN_VALUE; + @Nullable private String cipherSuite; + @Nullable private String cipherSpec; + @Nullable private String replyQueuePrefix; + @Nullable private String modelQueueName; + private String configurationQueueName = "SYSTEM.ADMIN.CONFIG.EVENT"; + private String performanceEventsQueueName = "SYSTEM.ADMIN.PERFM.EVENT"; + private String queueManagerEventsQueueName = "SYSTEM.ADMIN.QMGR.EVENT"; + private long consumeConfigurationEventInterval; + private boolean refreshQueueManagerConfigurationEnabled; + // Config default is 100. + // https://www.ibm.com/docs/en/ibm-mq/9.3.x?topic=qmini-channels-stanza-file + private int maxActiveChannels = 100; + + @Nullable private ResourceFilters queueFilters; + @Nullable private ResourceFilters channelFilters; + @Nullable private ResourceFilters listenerFilters; + @Nullable private ResourceFilters topicFilters; + + @Nullable + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public int getPort() { + return port; + } + + public void setPort(int port) { + this.port = port; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Nullable + public String getChannelName() { + return channelName; + } + + public void setChannelName(@Nullable String channelName) { + this.channelName = channelName; + } + + @Nullable + public String getTransportType() { + return transportType; + } + + public void setTransportType(@Nullable String transportType) { + this.transportType = transportType; + } + + @Nullable + public String getUsername() { + return username; + } + + public void setUsername(@Nullable String username) { + this.username = username; + } + + @Nullable + public String getPassword() { + return password; + } + + public void setPassword(@Nullable String password) { + this.password = password; + } + + public ResourceFilters getQueueFilters() { + if (queueFilters == null) { + return new ResourceFilters(); + } + return queueFilters; + } + + public void setQueueFilters(@Nullable ResourceFilters queueFilters) { + this.queueFilters = queueFilters; + } + + @Nullable + public String getSslKeyRepository() { + return sslKeyRepository; + } + + public void setSslKeyRepository(@Nullable String sslKeyRepository) { + this.sslKeyRepository = sslKeyRepository; + } + + @Nullable + public String getCipherSuite() { + return cipherSuite; + } + + public void setCipherSuite(String cipherSuite) { + this.cipherSuite = cipherSuite; + } + + @Nullable + public String getCipherSpec() { + return cipherSpec; + } + + public void setCipherSpec(@Nullable String cipherSpec) { + this.cipherSpec = cipherSpec; + } + + public ResourceFilters getChannelFilters() { + if (channelFilters == null) { + return new ResourceFilters(); + } + return channelFilters; + } + + public void setChannelFilters(@Nullable ResourceFilters channelFilters) { + this.channelFilters = channelFilters; + } + + @Nullable + public String getReplyQueuePrefix() { + return replyQueuePrefix; + } + + public void setReplyQueuePrefix(@Nullable String replyQueuePrefix) { + this.replyQueuePrefix = replyQueuePrefix; + } + + @Nullable + public String getModelQueueName() { + return modelQueueName; + } + + public void setModelQueueName(@Nullable String modelQueueName) { + this.modelQueueName = modelQueueName; + } + + public ResourceFilters getListenerFilters() { + if (listenerFilters == null) { + return new ResourceFilters(); + } + return listenerFilters; + } + + public void setListenerFilters(@Nullable ResourceFilters listenerFilters) { + this.listenerFilters = listenerFilters; + } + + public int getCcsid() { + return ccsid; + } + + public void setCcsid(int ccsid) { + this.ccsid = ccsid; + } + + public int getEncoding() { + return encoding; + } + + public void setEncoding(int encoding) { + this.encoding = encoding; + } + + public ResourceFilters getTopicFilters() { + if (topicFilters == null) { + return new ResourceFilters(); + } + return topicFilters; + } + + public void setTopicFilters(@Nullable ResourceFilters topicFilters) { + this.topicFilters = topicFilters; + } + + public String getConfigurationQueueName() { + return this.configurationQueueName; + } + + public void setConfigurationQueueName(String configurationQueueName) { + this.configurationQueueName = configurationQueueName; + } + + public long getConsumeConfigurationEventInterval() { + return this.consumeConfigurationEventInterval; + } + + public void setConsumeConfigurationEventInterval(long consumeConfigurationEventInterval) { + this.consumeConfigurationEventInterval = consumeConfigurationEventInterval; + } + + public boolean isRefreshQueueManagerConfigurationEnabled() { + return refreshQueueManagerConfigurationEnabled; + } + + public void setRefreshQueueManagerConfigurationEnabled( + boolean refreshQueueManagerConfigurationEnabled) { + this.refreshQueueManagerConfigurationEnabled = refreshQueueManagerConfigurationEnabled; + } + + public String getPerformanceEventsQueueName() { + return performanceEventsQueueName; + } + + public void setPerformanceEventsQueueName(String performanceEventsQueueName) { + this.performanceEventsQueueName = performanceEventsQueueName; + } + + public String getQueueManagerEventsQueueName() { + return this.queueManagerEventsQueueName; + } + + public void setQueueManagerEventsQueueName(String queueManagerEventsQueueName) { + this.queueManagerEventsQueueName = queueManagerEventsQueueName; + } + + public int getMaxActiveChannels() { + return maxActiveChannels; + } + + public void setMaxActiveChannels(int maxActiveChannels) { + this.maxActiveChannels = maxActiveChannels; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java new file mode 100644 index 000000000..72a3a9f42 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.config; + +import java.util.HashSet; +import java.util.Set; + +public class ResourceFilters { + + private Set include = new HashSet<>(); + private Set exclude = new HashSet<>(); + + public Set getInclude() { + return include; + } + + public void setInclude(Set include) { + this.include = include; + } + + public Set getExclude() { + return exclude; + } + + public void setExclude(Set exclude) { + this.exclude = exclude; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java new file mode 100644 index 000000000..6caf87c48 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metrics; + +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + +import io.opentelemetry.api.common.AttributeKey; + +// This file is generated using weaver. Do not edit manually. + +/** Attribute definitions generated from a Weaver model. Do not edit manually. */ +public final class IbmMqAttributes { + + /** The name of the IBM queue manager */ + public static final AttributeKey IBM_MQ_QUEUE_MANAGER = stringKey("ibm.mq.queue.manager"); + + /** The system-specific name of the messaging operation. */ + public static final AttributeKey MESSAGING_DESTINATION_NAME = + stringKey("messaging.destination.name"); + + /** The name of the channel */ + public static final AttributeKey IBM_MQ_CHANNEL_NAME = stringKey("ibm.mq.channel.name"); + + /** The type of the channel */ + public static final AttributeKey IBM_MQ_CHANNEL_TYPE = stringKey("ibm.mq.channel.type"); + + /** The job name */ + public static final AttributeKey IBM_MQ_JOB_NAME = stringKey("ibm.mq.job.name"); + + /** The start time of the channel as seconds since Epoch. */ + public static final AttributeKey IBM_MQ_CHANNEL_START_TIME = + longKey("ibm.mq.channel.start.time"); + + /** The queue type */ + public static final AttributeKey IBM_MQ_QUEUE_TYPE = stringKey("ibm.mq.queue.type"); + + /** The listener name */ + public static final AttributeKey IBM_MQ_LISTENER_NAME = stringKey("ibm.mq.listener.name"); + + /** Short name or login/username of the user. */ + public static final AttributeKey USER_NAME = stringKey("user.name"); + + /** Logical name of the service. */ + public static final AttributeKey SERVICE_NAME = stringKey("service.name"); + + private IbmMqAttributes() {} +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java new file mode 100644 index 000000000..c54c9a431 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java @@ -0,0 +1,424 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import java.util.function.Function; + +// This file is generated using weaver. Do not edit manually. + +/** Metric definitions generated from a Weaver model. Do not edit manually. */ +public final class Metrics { + public static final Function MIBY_TO_BYTES = x -> x * 1024L * 1024L; + + private Metrics() {} + + public static LongGauge createIbmMqMessageRetryCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.retry.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of message retries") + .build(); + } + + public static LongGauge createIbmMqStatus(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.status") + .ofLongs() + .setUnit("1") + .setDescription("Channel status") + .build(); + } + + public static LongGauge createIbmMqMaxSharingConversations(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.max.sharing.conversations") + .ofLongs() + .setUnit("{conversation}") + .setDescription("Maximum number of conversations permitted on this channel instance.") + .build(); + } + + public static LongGauge createIbmMqCurrentSharingConversations(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.current.sharing.conversations") + .ofLongs() + .setUnit("{conversation}") + .setDescription("Current number of conversations permitted on this channel instance.") + .build(); + } + + public static LongGauge createIbmMqByteReceived(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.byte.received") + .ofLongs() + .setUnit("By") + .setDescription("Number of bytes received") + .build(); + } + + public static LongGauge createIbmMqByteSent(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.byte.sent") + .ofLongs() + .setUnit("By") + .setDescription("Number of bytes sent") + .build(); + } + + public static LongGauge createIbmMqBuffersReceived(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.buffers.received") + .ofLongs() + .setUnit("{buffer}") + .setDescription("Buffers received") + .build(); + } + + public static LongGauge createIbmMqBuffersSent(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.buffers.sent") + .ofLongs() + .setUnit("{buffer}") + .setDescription("Buffers sent") + .build(); + } + + public static LongGauge createIbmMqMessageCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Message count") + .build(); + } + + public static LongGauge createIbmMqOpenInputCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.open.input.count") + .ofLongs() + .setUnit("{application}") + .setDescription("Count of applications sending messages to the queue") + .build(); + } + + public static LongGauge createIbmMqOpenOutputCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.open.output.count") + .ofLongs() + .setUnit("{application}") + .setDescription("Count of applications consuming messages from the queue") + .build(); + } + + public static LongGauge createIbmMqHighQueueDepth(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.high.queue.depth") + .ofLongs() + .setUnit("{percent}") + .setDescription("The current high queue depth") + .build(); + } + + public static LongGauge createIbmMqServiceInterval(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.service.interval") + .ofLongs() + .setUnit("{percent}") + .setDescription("The queue service interval") + .build(); + } + + public static LongCounter createIbmMqQueueDepthFullEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.queue.depth.full.event") + .setUnit("{event}") + .setDescription("The number of full queue events") + .build(); + } + + public static LongCounter createIbmMqQueueDepthHighEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.queue.depth.high.event") + .setUnit("{event}") + .setDescription("The number of high queue events") + .build(); + } + + public static LongCounter createIbmMqQueueDepthLowEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.queue.depth.low.event") + .setUnit("{event}") + .setDescription("The number of low queue events") + .build(); + } + + public static LongGauge createIbmMqUncommittedMessages(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.uncommitted.messages") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of uncommitted messages") + .build(); + } + + public static LongGauge createIbmMqOldestMsgAge(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.oldest.msg.age") + .ofLongs() + .setUnit("microseconds") + .setDescription("Queue message oldest age") + .build(); + } + + public static LongGauge createIbmMqCurrentMaxQueueFilesize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.current.max.queue.filesize") + .ofLongs() + .setUnit("By") + .setDescription("Current maximum queue file size") + .build(); + } + + public static LongGauge createIbmMqCurrentQueueFilesize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.current.queue.filesize") + .ofLongs() + .setUnit("By") + .setDescription("Current queue file size") + .build(); + } + + public static LongGauge createIbmMqInstancesPerClient(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.instances.per.client") + .ofLongs() + .setUnit("{instance}") + .setDescription("Instances per client") + .build(); + } + + public static LongGauge createIbmMqMessageDeqCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.deq.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Message dequeue count") + .build(); + } + + public static LongGauge createIbmMqMessageEnqCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.enq.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Message enqueue count") + .build(); + } + + public static LongGauge createIbmMqQueueDepth(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.queue.depth") + .ofLongs() + .setUnit("{message}") + .setDescription("Current queue depth") + .build(); + } + + public static LongGauge createIbmMqServiceIntervalEvent(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.service.interval.event") + .ofLongs() + .setUnit("1") + .setDescription("Queue service interval event") + .build(); + } + + public static LongGauge createIbmMqReusableLogSize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.reusable.log.size") + .ofLongs() + .setUnit("By") + .setDescription( + "The amount of space occupied, in megabytes, by log extents available to be reused.") + .build(); + } + + public static LongGauge createIbmMqManagerActiveChannels(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.active.channels") + .ofLongs() + .setUnit("{channel}") + .setDescription("The queue manager active maximum channels limit") + .build(); + } + + public static LongGauge createIbmMqRestartLogSize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.restart.log.size") + .ofLongs() + .setUnit("By") + .setDescription("Size of the log data required for restart recovery in megabytes.") + .build(); + } + + public static LongGauge createIbmMqMaxQueueDepth(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.max.queue.depth") + .ofLongs() + .setUnit("{message}") + .setDescription("Maximum queue depth") + .build(); + } + + public static LongGauge createIbmMqOnqtimeShortPeriod(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.onqtime.short_period") + .ofLongs() + .setUnit("microseconds") + .setDescription( + "Amount of time, in microseconds, that a message spent on the queue, over a short period") + .build(); + } + + public static LongGauge createIbmMqOnqtimeLongPeriod(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.onqtime.long_period") + .ofLongs() + .setUnit("microseconds") + .setDescription( + "Amount of time, in microseconds, that a message spent on the queue, over a longer period") + .build(); + } + + public static LongGauge createIbmMqMessageReceivedCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.received.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of messages received") + .build(); + } + + public static LongGauge createIbmMqMessageSentCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.message.sent.count") + .ofLongs() + .setUnit("{message}") + .setDescription("Number of messages sent") + .build(); + } + + public static LongGauge createIbmMqMaxInstances(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.max.instances") + .ofLongs() + .setUnit("{instance}") + .setDescription("Max channel instances") + .build(); + } + + public static LongGauge createIbmMqConnectionCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.connection.count") + .ofLongs() + .setUnit("{connection}") + .setDescription("Active connections count") + .build(); + } + + public static LongGauge createIbmMqManagerStatus(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.status") + .ofLongs() + .setUnit("1") + .setDescription("Queue manager status") + .build(); + } + + public static LongGauge createIbmMqHeartbeat(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.heartbeat") + .ofLongs() + .setUnit("1") + .setDescription("Queue manager heartbeat") + .build(); + } + + public static LongGauge createIbmMqArchiveLogSize(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.archive.log.size") + .ofLongs() + .setUnit("By") + .setDescription("Queue manager archive log size") + .build(); + } + + public static LongGauge createIbmMqManagerMaxActiveChannels(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.max.active.channels") + .ofLongs() + .setUnit("{channel}") + .setDescription("Queue manager max active channels") + .build(); + } + + public static LongGauge createIbmMqManagerStatisticsInterval(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.statistics.interval") + .ofLongs() + .setUnit("1") + .setDescription("Queue manager statistics interval") + .build(); + } + + public static LongGauge createIbmMqPublishCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.publish.count") + .ofLongs() + .setUnit("{publication}") + .setDescription("Topic publication count") + .build(); + } + + public static LongGauge createIbmMqSubscriptionCount(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.subscription.count") + .ofLongs() + .setUnit("{subscription}") + .setDescription("Topic subscription count") + .build(); + } + + public static LongGauge createIbmMqListenerStatus(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.listener.status") + .ofLongs() + .setUnit("1") + .setDescription("Listener status") + .build(); + } + + public static LongCounter createIbmMqUnauthorizedEvent(Meter meter) { + return meter + .counterBuilder("ibm.mq.unauthorized.event") + .setUnit("{event}") + .setDescription("Number of authentication error events") + .build(); + } + + public static LongGauge createIbmMqManagerMaxHandles(Meter meter) { + return meter + .gaugeBuilder("ibm.mq.manager.max.handles") + .ofLongs() + .setUnit("{event}") + .setDescription("Max open handles") + .build(); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java new file mode 100644 index 000000000..c172cd532 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java @@ -0,0 +1,213 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.Map; + +// This file is generated using weaver. Do not edit manually. + +/** Configuration of metrics as defined in config.yml. */ +public final class MetricsConfig { + + private final Map config; + + public MetricsConfig(ConfigWrapper config) { + this.config = config.getMetrics(); + } + + public boolean isIbmMqMessageRetryCountEnabled() { + return isEnabled("ibm.mq.message.retry.count"); + } + + public boolean isIbmMqStatusEnabled() { + return isEnabled("ibm.mq.status"); + } + + public boolean isIbmMqMaxSharingConversationsEnabled() { + return isEnabled("ibm.mq.max.sharing.conversations"); + } + + public boolean isIbmMqCurrentSharingConversationsEnabled() { + return isEnabled("ibm.mq.current.sharing.conversations"); + } + + public boolean isIbmMqByteReceivedEnabled() { + return isEnabled("ibm.mq.byte.received"); + } + + public boolean isIbmMqByteSentEnabled() { + return isEnabled("ibm.mq.byte.sent"); + } + + public boolean isIbmMqBuffersReceivedEnabled() { + return isEnabled("ibm.mq.buffers.received"); + } + + public boolean isIbmMqBuffersSentEnabled() { + return isEnabled("ibm.mq.buffers.sent"); + } + + public boolean isIbmMqMessageCountEnabled() { + return isEnabled("ibm.mq.message.count"); + } + + public boolean isIbmMqOpenInputCountEnabled() { + return isEnabled("ibm.mq.open.input.count"); + } + + public boolean isIbmMqOpenOutputCountEnabled() { + return isEnabled("ibm.mq.open.output.count"); + } + + public boolean isIbmMqHighQueueDepthEnabled() { + return isEnabled("ibm.mq.high.queue.depth"); + } + + public boolean isIbmMqServiceIntervalEnabled() { + return isEnabled("ibm.mq.service.interval"); + } + + public boolean isIbmMqQueueDepthFullEventEnabled() { + return isEnabled("ibm.mq.queue.depth.full.event"); + } + + public boolean isIbmMqQueueDepthHighEventEnabled() { + return isEnabled("ibm.mq.queue.depth.high.event"); + } + + public boolean isIbmMqQueueDepthLowEventEnabled() { + return isEnabled("ibm.mq.queue.depth.low.event"); + } + + public boolean isIbmMqUncommittedMessagesEnabled() { + return isEnabled("ibm.mq.uncommitted.messages"); + } + + public boolean isIbmMqOldestMsgAgeEnabled() { + return isEnabled("ibm.mq.oldest.msg.age"); + } + + public boolean isIbmMqCurrentMaxQueueFilesizeEnabled() { + return isEnabled("ibm.mq.current.max.queue.filesize"); + } + + public boolean isIbmMqCurrentQueueFilesizeEnabled() { + return isEnabled("ibm.mq.current.queue.filesize"); + } + + public boolean isIbmMqInstancesPerClientEnabled() { + return isEnabled("ibm.mq.instances.per.client"); + } + + public boolean isIbmMqMessageDeqCountEnabled() { + return isEnabled("ibm.mq.message.deq.count"); + } + + public boolean isIbmMqMessageEnqCountEnabled() { + return isEnabled("ibm.mq.message.enq.count"); + } + + public boolean isIbmMqQueueDepthEnabled() { + return isEnabled("ibm.mq.queue.depth"); + } + + public boolean isIbmMqServiceIntervalEventEnabled() { + return isEnabled("ibm.mq.service.interval.event"); + } + + public boolean isIbmMqReusableLogSizeEnabled() { + return isEnabled("ibm.mq.reusable.log.size"); + } + + public boolean isIbmMqManagerActiveChannelsEnabled() { + return isEnabled("ibm.mq.manager.active.channels"); + } + + public boolean isIbmMqRestartLogSizeEnabled() { + return isEnabled("ibm.mq.restart.log.size"); + } + + public boolean isIbmMqMaxQueueDepthEnabled() { + return isEnabled("ibm.mq.max.queue.depth"); + } + + public boolean isIbmMqOnqtimeShortPeriodEnabled() { + return isEnabled("ibm.mq.onqtime.short_period"); + } + + public boolean isIbmMqOnqtimeLongPeriodEnabled() { + return isEnabled("ibm.mq.onqtime.long_period"); + } + + public boolean isIbmMqMessageReceivedCountEnabled() { + return isEnabled("ibm.mq.message.received.count"); + } + + public boolean isIbmMqMessageSentCountEnabled() { + return isEnabled("ibm.mq.message.sent.count"); + } + + public boolean isIbmMqMaxInstancesEnabled() { + return isEnabled("ibm.mq.max.instances"); + } + + public boolean isIbmMqConnectionCountEnabled() { + return isEnabled("ibm.mq.connection.count"); + } + + public boolean isIbmMqManagerStatusEnabled() { + return isEnabled("ibm.mq.manager.status"); + } + + public boolean isIbmMqHeartbeatEnabled() { + return isEnabled("ibm.mq.heartbeat"); + } + + public boolean isIbmMqArchiveLogSizeEnabled() { + return isEnabled("ibm.mq.archive.log.size"); + } + + public boolean isIbmMqManagerMaxActiveChannelsEnabled() { + return isEnabled("ibm.mq.manager.max.active.channels"); + } + + public boolean isIbmMqManagerStatisticsIntervalEnabled() { + return isEnabled("ibm.mq.manager.statistics.interval"); + } + + public boolean isIbmMqPublishCountEnabled() { + return isEnabled("ibm.mq.publish.count"); + } + + public boolean isIbmMqSubscriptionCountEnabled() { + return isEnabled("ibm.mq.subscription.count"); + } + + public boolean isIbmMqListenerStatusEnabled() { + return isEnabled("ibm.mq.listener.status"); + } + + public boolean isIbmMqUnauthorizedEventEnabled() { + return isEnabled("ibm.mq.unauthorized.event"); + } + + public boolean isIbmMqManagerMaxHandlesEnabled() { + return isEnabled("ibm.mq.manager.max.handles"); + } + + private boolean isEnabled(String key) { + Object metricInfo = config.get(key); + if (!(metricInfo instanceof Map)) { + return false; + } + Object enabled = ((Map) metricInfo).get("enabled"); + if (enabled instanceof Boolean) { + return (Boolean) enabled; + } + return false; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java new file mode 100644 index 000000000..44afdb28a --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollector.java @@ -0,0 +1,231 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static com.ibm.mq.constants.CMQC.MQRC_SELECTOR_ERROR; +import static com.ibm.mq.constants.CMQCFC.MQRCCF_CHL_STATUS_NOT_FOUND; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_START_TIME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_TYPE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_JOB_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for channel metric collection. */ +public final class ChannelMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(ChannelMetricsCollector.class); + + private final LongGauge activeChannelsGauge; + private final LongGauge channelStatusGauge; + private final LongGauge messageCountGauge; + private final LongGauge byteSentGauge; + private final LongGauge byteReceivedGauge; + private final LongGauge buffersSentGauge; + private final LongGauge buffersReceivedGauge; + private final LongGauge currentSharingConvsGauge; + private final LongGauge maxSharingConvsGauge; + + /* + * The Channel Status values are mentioned here http://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.dev.doc/q090880_.htm + */ + public ChannelMetricsCollector(Meter meter) { + this.activeChannelsGauge = Metrics.createIbmMqManagerActiveChannels(meter); + this.channelStatusGauge = Metrics.createIbmMqStatus(meter); + this.messageCountGauge = Metrics.createIbmMqMessageCount(meter); + this.byteSentGauge = Metrics.createIbmMqByteSent(meter); + this.byteReceivedGauge = Metrics.createIbmMqByteReceived(meter); + this.buffersSentGauge = Metrics.createIbmMqBuffersSent(meter); + this.buffersReceivedGauge = Metrics.createIbmMqBuffersReceived(meter); + this.currentSharingConvsGauge = Metrics.createIbmMqCurrentSharingConversations(meter); + this.maxSharingConvsGauge = Metrics.createIbmMqMaxSharingConversations(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command MQCMD_INQUIRE_CHANNEL_STATUS"); + long entryTime = System.currentTimeMillis(); + + int[] attrs = + new int[] { + CMQCFC.MQCACH_CHANNEL_NAME, + CMQCFC.MQCACH_CONNECTION_NAME, + CMQCFC.MQIACH_CHANNEL_TYPE, + CMQCFC.MQIACH_MSGS, + CMQCFC.MQIACH_CHANNEL_STATUS, + CMQCFC.MQIACH_BYTES_SENT, + CMQCFC.MQIACH_BYTES_RECEIVED, + CMQCFC.MQIACH_BUFFERS_SENT, + CMQCFC.MQIACH_BUFFERS_RECEIVED, + CMQCFC.MQIACH_CURRENT_SHARING_CONVS, + CMQCFC.MQIACH_MAX_SHARING_CONVS, + CMQCFC.MQCACH_CHANNEL_START_DATE, + CMQCFC.MQCACH_CHANNEL_START_TIME, + CMQCFC.MQCACH_MCA_JOB_NAME + }; + if (logger.isDebugEnabled()) { + logger.debug( + "Attributes being sent along PCF agent request to query channel metrics: {}", + Arrays.toString(attrs)); + } + + Set channelGenericNames = context.getChannelIncludeFilterNames(); + + // + // The MQCMD_INQUIRE_CHANNEL_STATUS command queries the current operational status of channels. + // This includes information about whether a channel is running, stopped, or in another state, + // as well as details about the channel’s performance and usage. + List activeChannels = new ArrayList<>(); + for (String channelGenericName : channelGenericNames) { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS); + request.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, channelGenericName); + request.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, CMQC.MQOT_CURRENT_CHANNEL); + request.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_ATTRS, attrs); + try { + logger.debug( + "sending PCF agent request to query metrics for generic channel {}", + channelGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queue metrics query response for generic queue {} received in {} milliseconds", + channelGenericName, + endTime); + if (response.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("channel") + .excluding(context.getChannelExcludeFilters()) + .withResourceExtractor(MessageBuddy::channelName) + .filter(response); + + for (PCFMessage message : messages) { + String channelName = MessageBuddy.channelName(message); + String channelType = MessageBuddy.channelType(message); + long channelStartTime = MessageBuddy.channelStartTime(message); + String jobName = MessageBuddy.jobName(message); + + logger.debug("Pulling out metrics for channel name {}", channelName); + updateMetrics( + context, + message, + channelName, + channelType, + channelStartTime, + jobName, + activeChannels); + } + } catch (PCFException pcfe) { + if (pcfe.getReason() == MQRCCF_CHL_STATUS_NOT_FOUND) { + String errorMsg = "Channel- " + channelGenericName + " :"; + errorMsg += + "Could not collect channel information as channel is stopped or inactive: Reason '3065'\n"; + errorMsg += + "If the channel type is MQCHT_RECEIVER, MQCHT_SVRCONN or MQCHT_CLUSRCVR, then the only action is to enable the channel, not start it."; + logger.error(errorMsg, pcfe); + } else if (pcfe.getReason() == MQRC_SELECTOR_ERROR) { + logger.error( + "Invalid metrics passed while collecting channel metrics, check config.yaml: Reason '2067'", + pcfe); + } else { + logger.error(pcfe.getMessage(), pcfe); + } + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting metrics for channel " + channelGenericName, + e); + } + } + + logger.info( + "Active Channels in queueManager {} are {}", context.getQueueManagerName(), activeChannels); + activeChannelsGauge.set( + activeChannels.size(), Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName())); + + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for all channels is {} milliseconds", exitTime); + } + + private void updateMetrics( + MetricsCollectorContext context, + PCFMessage message, + String channelName, + String channelType, + long channelStartTime, + String jobName, + List activeChannels) + throws PCFException { + Attributes attributes = + Attributes.builder() + .put(IBM_MQ_CHANNEL_NAME, channelName) + .put(IBM_MQ_CHANNEL_TYPE, channelType) + .put(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName()) + .put(IBM_MQ_CHANNEL_START_TIME, channelStartTime) + .put(IBM_MQ_JOB_NAME, jobName) + .build(); + if (context.getMetricsConfig().isIbmMqMessageCountEnabled()) { + int received = message.getIntParameterValue(CMQCFC.MQIACH_MSGS); + messageCountGauge.set(received, attributes); + } + int status = message.getIntParameterValue(CMQCFC.MQIACH_CHANNEL_STATUS); + if (context.getMetricsConfig().isIbmMqStatusEnabled()) { + channelStatusGauge.set(status, attributes); + } + // We follow the definition of active channel as documented in + // https://www.ibm.com/docs/en/ibm-mq/9.2.x?topic=states-current-active + if (status != CMQCFC.MQCHS_RETRYING + && status != CMQCFC.MQCHS_STOPPED + && status != CMQCFC.MQCHS_STARTING) { + activeChannels.add(channelName); + } + if (context.getMetricsConfig().isIbmMqByteSentEnabled()) { + byteSentGauge.set(message.getIntParameterValue(CMQCFC.MQIACH_BYTES_SENT), attributes); + } + if (context.getMetricsConfig().isIbmMqByteReceivedEnabled()) { + byteReceivedGauge.set(message.getIntParameterValue(CMQCFC.MQIACH_BYTES_RECEIVED), attributes); + } + if (context.getMetricsConfig().isIbmMqBuffersSentEnabled()) { + buffersSentGauge.set(message.getIntParameterValue(CMQCFC.MQIACH_BUFFERS_SENT), attributes); + } + if (context.getMetricsConfig().isIbmMqBuffersReceivedEnabled()) { + buffersReceivedGauge.set( + message.getIntParameterValue(CMQCFC.MQIACH_BUFFERS_RECEIVED), attributes); + } + if (context.getMetricsConfig().isIbmMqCurrentSharingConversationsEnabled()) { + int currentSharingConvs = 0; + if (message.getParameter(CMQCFC.MQIACH_CURRENT_SHARING_CONVS) != null) { + currentSharingConvs = message.getIntParameterValue(CMQCFC.MQIACH_CURRENT_SHARING_CONVS); + } + currentSharingConvsGauge.set(currentSharingConvs, attributes); + } + if (context.getMetricsConfig().isIbmMqMaxSharingConversationsEnabled()) { + int maxSharingConvs = 0; + if (message.getParameter(CMQCFC.MQIACH_MAX_SHARING_CONVS) != null) { + maxSharingConvs = message.getIntParameterValue(CMQCFC.MQIACH_MAX_SHARING_CONVS); + } + maxSharingConvsGauge.set(maxSharingConvs, attributes); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java new file mode 100644 index 000000000..668ba2ac0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/FilterType.java @@ -0,0 +1,14 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +public enum FilterType { + STARTSWITH, + EQUALS, + ENDSWITH, + CONTAINS, + NONE +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java new file mode 100644 index 000000000..b0dc7286c --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollector.java @@ -0,0 +1,149 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_CHANNEL_TYPE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.MQCFIL; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for channel inquiry metric collection. */ +public final class InquireChannelCmdCollector implements Consumer { + + public static final Logger logger = LoggerFactory.getLogger(InquireChannelCmdCollector.class); + private final LongGauge maxClientsGauge; + private final LongGauge instancesPerClientGauge; + private final LongGauge messageRetryCountGauge; + private final LongGauge messageReceivedCountGauge; + private final LongGauge messageSentCountGauge; + + public InquireChannelCmdCollector(Meter meter) { + this.maxClientsGauge = Metrics.createIbmMqMaxInstances(meter); + this.instancesPerClientGauge = Metrics.createIbmMqInstancesPerClient(meter); + this.messageRetryCountGauge = Metrics.createIbmMqMessageRetryCount(meter); + this.messageReceivedCountGauge = Metrics.createIbmMqMessageReceivedCount(meter); + this.messageSentCountGauge = Metrics.createIbmMqMessageSentCount(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + + Set channelGenericNames = context.getChannelIncludeFilterNames(); + + for (String channelGenericName : channelGenericNames) { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_CHANNEL); + request.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, channelGenericName); + request.addParameter( + new MQCFIL(MQConstants.MQIACF_CHANNEL_ATTRS, new int[] {MQConstants.MQIACF_ALL})); + try { + logger.debug( + "sending PCF agent request to query metrics for generic channel {}", + channelGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queue metrics query response for generic queue {} received in {} milliseconds", + channelGenericName, + endTime); + if (response.isEmpty()) { + logger.warn("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("channel") + .excluding(context.getChannelExcludeFilters()) + .withResourceExtractor(MessageBuddy::channelName) + .filter(response); + + for (PCFMessage message : messages) { + String channelName = MessageBuddy.channelName(message); + String channelType = MessageBuddy.channelType(message); + logger.debug("Pulling out metrics for channel name {}", channelName); + updateMetrics(message, channelName, channelType, context); + } + } catch (PCFException pcfe) { + if (pcfe.getReason() == MQConstants.MQRCCF_CHL_STATUS_NOT_FOUND) { + String errorMsg = "Channel- " + channelGenericName + " :"; + errorMsg += + "Could not collect channel information as channel is stopped or inactive: Reason '3065'\n"; + errorMsg += + "If the channel type is MQCHT_RECEIVER, MQCHT_SVRCONN or MQCHT_CLUSRCVR, then the only action is to enable the channel, not start it."; + logger.error(errorMsg, pcfe); + } else if (pcfe.getReason() == MQConstants.MQRC_SELECTOR_ERROR) { + logger.error( + "Invalid metrics passed while collecting channel metrics, check config.yaml: Reason '2067'", + pcfe); + } + logger.error(pcfe.getMessage(), pcfe); + } catch (Exception e) { + logger.error( + "Unexpected error while collecting metrics for channel " + channelGenericName, e); + } + } + + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for all channels is {} milliseconds", exitTime); + } + + private void updateMetrics( + PCFMessage message, String channelName, String channelType, MetricsCollectorContext context) + throws PCFException { + Attributes attributes = + Attributes.builder() + .put(IBM_MQ_CHANNEL_NAME, channelName) + .put(IBM_MQ_CHANNEL_TYPE, channelType) + .put(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName()) + .build(); + if (context.getMetricsConfig().isIbmMqMaxInstancesEnabled() + && message.getParameter(CMQCFC.MQIACH_MAX_INSTANCES) != null) { + this.maxClientsGauge.set( + message.getIntParameterValue(CMQCFC.MQIACH_MAX_INSTANCES), attributes); + } + if (context.getMetricsConfig().isIbmMqInstancesPerClientEnabled() + && message.getParameter(CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT) != null) { + this.instancesPerClientGauge.set( + message.getIntParameterValue(CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT), attributes); + } + if (context.getMetricsConfig().isIbmMqMessageRetryCountEnabled()) { + int count = 0; + if (message.getParameter(CMQCFC.MQIACH_MR_COUNT) != null) { + count = message.getIntParameterValue(CMQCFC.MQIACH_MR_COUNT); + } + this.messageRetryCountGauge.set(count, attributes); + } + if (context.getMetricsConfig().isIbmMqInstancesPerClientEnabled()) { + int received = 0; + if (message.getParameter(CMQCFC.MQIACH_MSGS_RECEIVED) != null) { + received = message.getIntParameterValue(CMQCFC.MQIACH_MSGS_RECEIVED); + } + this.messageReceivedCountGauge.set(received, attributes); + } + if (context.getMetricsConfig().isIbmMqMessageSentCountEnabled()) { + int sent = 0; + if (message.getParameter(CMQCFC.MQIACH_MSGS_SENT) != null) { + sent = message.getIntParameterValue(CMQCFC.MQIACH_MSGS_SENT); + } + this.messageSentCountGauge.set(sent, attributes); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java new file mode 100644 index 000000000..16164b46d --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQCmdCollector.java @@ -0,0 +1,69 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class InquireQCmdCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(InquireQCmdCollector.class); + + static final int[] ATTRIBUTES = + new int[] { + CMQC.MQCA_Q_NAME, + CMQC.MQIA_USAGE, + CMQC.MQIA_Q_TYPE, + CMQC.MQIA_CURRENT_Q_DEPTH, + CMQC.MQIA_MAX_Q_DEPTH, + CMQC.MQIA_OPEN_INPUT_COUNT, + CMQC.MQIA_OPEN_OUTPUT_COUNT, + CMQC.MQIA_Q_SERVICE_INTERVAL, + CMQC.MQIA_Q_SERVICE_INTERVAL_EVENT + }; + + static final String COMMAND = "MQCMD_INQUIRE_Q"; + private final QueueCollectionBuddy queueBuddy; + + public InquireQCmdCollector(QueueCollectionBuddy queueBuddy) { + this.queueBuddy = queueBuddy; + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command {}", COMMAND); + long entryTime = System.currentTimeMillis(); + + logger.debug( + "Attributes being sent along PCF agent request to query queue metrics: {} for command {}", + Arrays.toString(ATTRIBUTES), + COMMAND); + + Set queueGenericNames = context.getQueueIncludeFilterNames(); + for (String queueGenericName : queueGenericNames) { + // list of all metrics extracted through MQCMD_INQUIRE_Q is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q087810_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q); + request.addParameter(CMQC.MQCA_Q_NAME, queueGenericName); + request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_ALL); + request.addParameter(CMQCFC.MQIACF_Q_ATTRS, ATTRIBUTES); + + queueBuddy.processPcfRequestAndPublishQMetrics( + context, request, queueGenericName, ATTRIBUTES); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command {}", + exitTime, + COMMAND); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java new file mode 100644 index 000000000..3580c02b1 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQStatusCmdCollector.java @@ -0,0 +1,74 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The InquireQStatusCmdCollector class is responsible for collecting and publishing queue metrics + * using the IBM MQ command `MQCMD_INQUIRE_Q_STATUS`. It extends the QueueMetricsCollector class and + * implements the Runnable interface, enabling execution within a separate thread. + * + *

This class interacts with PCF (Programmable Command Formats) messages to query queue metrics + * based on the configuration provided. It retrieves status information about a queue, such as: • + * The number of messages on the queue • Open handles (how many apps have it open) • Whether the + * queue is in use for input/output • Last get/put timestamps • And other real-time statistics + * + *

Thread Safety: This class is thread-safe, as it operates independently with state shared only + * through immutable or synchronized structures where necessary. + * + *

Usage: - Instantiate this class by providing an existing QueueMetricsCollector instance, a map + * of metrics to report, and shared state. - Invoke the run method to execute the queue metrics + * collection process. + */ +final class InquireQStatusCmdCollector implements Consumer { + + static final int[] ATTRIBUTES = + new int[] { + CMQC.MQCA_Q_NAME, + CMQCFC.MQIACF_CUR_Q_FILE_SIZE, + CMQCFC.MQIACF_CUR_MAX_FILE_SIZE, + CMQCFC.MQIACF_OLDEST_MSG_AGE, + CMQCFC.MQIACF_UNCOMMITTED_MSGS, + CMQCFC.MQIACF_Q_TIME_INDICATOR, + CMQC.MQIA_CURRENT_Q_DEPTH, + }; + + private static final Logger logger = LoggerFactory.getLogger(InquireQStatusCmdCollector.class); + + private final QueueCollectionBuddy queueBuddy; + + InquireQStatusCmdCollector(QueueCollectionBuddy queueBuddy) { + this.queueBuddy = queueBuddy; + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command MQCMD_INQUIRE_Q_STATUS"); + long entryTime = System.currentTimeMillis(); + + Set queueGenericNames = context.getQueueIncludeFilterNames(); + for (String queueGenericName : queueGenericNames) { + // list of all metrics extracted through MQCMD_INQUIRE_Q_STATUS is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q087880_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_STATUS); + request.addParameter(CMQC.MQCA_Q_NAME, queueGenericName); + request.addParameter(CMQCFC.MQIACF_Q_STATUS_ATTRS, ATTRIBUTES); + queueBuddy.processPcfRequestAndPublishQMetrics( + context, request, queueGenericName, ATTRIBUTES); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command MQCMD_INQUIRE_Q_STATUS", + exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java new file mode 100644 index 000000000..5cdf0c52f --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireQueueManagerCmdCollector.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.MQCFIL; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.List; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for queue metric collection. */ +public final class InquireQueueManagerCmdCollector implements Consumer { + + private static final Logger logger = + LoggerFactory.getLogger(InquireQueueManagerCmdCollector.class); + private final LongGauge statisticsIntervalGauge; + + public InquireQueueManagerCmdCollector(Meter meter) { + this.statisticsIntervalGauge = Metrics.createIbmMqManagerStatisticsInterval(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + logger.debug( + "publishMetrics entry time for queuemanager {} is {} milliseconds", + context.getQueueManagerName(), + entryTime); + // CMQCFC.MQCMD_INQUIRE_Q_MGR is 2 + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_MGR); + // request.addParameter(CMQC.MQCA_Q_MGR_NAME, "*"); + // CMQCFC.MQIACF_Q_MGR_STATUS_ATTRS is 1001 + request.addParameter( + new MQCFIL(MQConstants.MQIACF_Q_MGR_ATTRS, new int[] {MQConstants.MQIACF_ALL})); + try { + // Note that agent.send() method is synchronized + logger.debug( + "sending PCF agent request to query queuemanager {}", context.getQueueManagerName()); + long startTime = System.currentTimeMillis(); + List responses = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queuemanager metrics query response for {} received in {} milliseconds", + context.getQueueManagerName(), + endTime); + if (responses.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is either null or empty"); + return; + } + if (context.getMetricsConfig().isIbmMqManagerStatisticsIntervalEnabled()) { + int interval = responses.get(0).getIntParameterValue(CMQC.MQIA_STATISTICS_INTERVAL); + statisticsIntervalGauge.set( + interval, Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName())); + } + } catch (Exception e) { + logger.error("Error collecting QueueManagerCmd metrics", e); + throw new IllegalStateException(e); + } finally { + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for queuemanager is {} milliseconds", exitTime); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java new file mode 100644 index 000000000..bb3812565 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/InquireTStatusCmdCollector.java @@ -0,0 +1,136 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class InquireTStatusCmdCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(InquireTStatusCmdCollector.class); + + private final LongGauge publishCountGauge; + private final LongGauge subscriptionCountGauge; + + public InquireTStatusCmdCollector(Meter meter) { + this.publishCountGauge = Metrics.createIbmMqPublishCount(meter); + this.subscriptionCountGauge = Metrics.createIbmMqSubscriptionCount(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command MQCMD_INQUIRE_TOPIC_STATUS"); + long entryTime = System.currentTimeMillis(); + + Set topicGenericNames = context.getTopicIncludeFilterNames(); + // to query the current status of topics, which is essential for monitoring and managing the + // publish/subscribe environment in IBM MQ. + for (String topicGenericName : topicGenericNames) { + // Request: + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q088140_.htm + // list of all metrics extracted through MQCMD_INQUIRE_TOPIC_STATUS is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q088150_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS); + request.addParameter(CMQC.MQCA_TOPIC_STRING, topicGenericName); + + try { + processPcfRequestAndPublishQMetrics(context, topicGenericName, request); + } catch (PCFException pcfe) { + logger.error( + "PCFException caught while collecting metric for Queue: {} for command MQCMD_INQUIRE_TOPIC_STATUS", + topicGenericName, + pcfe); + PCFMessage[] msgs = (PCFMessage[]) pcfe.exceptionSource; + for (PCFMessage msg : msgs) { + logger.error(msg.toString()); + } + // Don't throw exception as it will stop queue metric colloection + } catch (Exception mqe) { + logger.error("MQException caught", mqe); + // Dont throw exception as it will stop queuemetric colloection + } + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command MQCMD_INQUIRE_TOPIC_STATUS", + exitTime); + } + + private void processPcfRequestAndPublishQMetrics( + MetricsCollectorContext context, String topicGenericName, PCFMessage request) + throws IOException, MQDataException { + logger.debug( + "sending PCF agent request to topic metrics for generic topic {} for command MQCMD_INQUIRE_TOPIC_STATUS", + topicGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent topic metrics query response for generic topic {} for command MQCMD_INQUIRE_TOPIC_STATUS received in {} milliseconds", + topicGenericName, + endTime); + if (response.isEmpty()) { + logger.debug( + "Unexpected error while PCFMessage.send() for command MQCMD_INQUIRE_TOPIC_STATUS, response is either null or empty"); + return; + } + + List messages = + MessageFilter.ofKind("topic") + .excluding(context.getTopicExcludeFilters()) + .withResourceExtractor(MessageBuddy::topicName) + .filter(response); + + for (PCFMessage message : messages) { + String topicName = MessageBuddy.topicName(message); + logger.debug( + "Pulling out metrics for topic name {} for command MQCMD_INQUIRE_TOPIC_STATUS", + topicName); + extractMetrics(context, message, topicName); + } + } + + private void extractMetrics( + MetricsCollectorContext context, PCFMessage pcfMessage, String topicString) + throws PCFException { + Attributes attributes = + Attributes.of( + MESSAGING_DESTINATION_NAME, + topicString, + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName()); + if (context.getMetricsConfig().isIbmMqPublishCountEnabled()) { + int publisherCount = 0; + if (pcfMessage.getParameter(CMQC.MQIA_PUB_COUNT) != null) { + publisherCount = pcfMessage.getIntParameterValue(CMQC.MQIA_PUB_COUNT); + } + publishCountGauge.set(publisherCount, attributes); + } + if (context.getMetricsConfig().isIbmMqSubscriptionCountEnabled()) { + int subscriberCount = 0; + if (pcfMessage.getParameter(CMQC.MQIA_SUB_COUNT) != null) { + subscriberCount = pcfMessage.getIntParameterValue(CMQC.MQIA_SUB_COUNT); + } + subscriptionCountGauge.set(subscriberCount, attributes); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java new file mode 100644 index 000000000..a7bf3d48d --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollector.java @@ -0,0 +1,113 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_LISTENER_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ListenerMetricsCollector is a specialized implementation of the MetricsCollector that is + * responsible for collecting and publishing metrics related to IBM MQ Listeners. + * + *

This class interacts with PCFMessageAgent to query metrics for specific listeners, applies + * "include:" and "exclude:" listenerFilters defined in config yaml, and uses MetricWriteHelper to + * publish the collected metrics in the required format. + * + *

Key functionalities include: • query using PCF Command: MQCMD_INQUIRE_LISTENER_STATUS to get + * the status of one or more listeners on a queue manager. • retrieve tcp/ip listeners runtime + * information such as: - listener is running or stopped - port number and transport type - last + * error codes - associated command server • + * + *

It utilizes WMQMetricOverride to map metrics from the configuration to their IBM MQ constants. + */ +public final class ListenerMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(ListenerMetricsCollector.class); + private final LongGauge listenerStatusGauge; + + public ListenerMetricsCollector(Meter meter) { + this.listenerStatusGauge = Metrics.createIbmMqListenerStatus(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + + int[] attrs = new int[] {CMQCFC.MQCACH_LISTENER_NAME, CMQCFC.MQIACH_LISTENER_STATUS}; + logger.debug( + "Attributes being sent along PCF agent request to query channel metrics: " + + Arrays.toString(attrs)); + + Set listenerGenericNames = context.getListenerIncludeFilterNames(); + for (String listenerGenericName : listenerGenericNames) { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS); + request.addParameter(CMQCFC.MQCACH_LISTENER_NAME, listenerGenericName); + request.addParameter(CMQCFC.MQIACF_LISTENER_STATUS_ATTRS, attrs); + try { + logger.debug( + "sending PCF agent request to query metrics for generic listener {}", + listenerGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent listener metrics query response for generic listener {} received in {} milliseconds", + listenerGenericName, + endTime); + if (response.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("listener") + .excluding(context.getListenerExcludeFilters()) + .withResourceExtractor(MessageBuddy::listenerName) + .filter(response); + + for (PCFMessage message : messages) { + String listenerName = MessageBuddy.listenerName(message); + logger.debug("Pulling out metrics for listener name {}", listenerName); + updateMetrics(message, listenerName, context); + } + } catch (Exception e) { + logger.error( + "Unexpected error while collecting metrics for listener " + listenerGenericName, e); + } + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for all listener is {} milliseconds", exitTime); + } + + private void updateMetrics( + PCFMessage message, String listenerName, MetricsCollectorContext context) + throws PCFException { + if (context.getMetricsConfig().isIbmMqListenerStatusEnabled()) { + int status = message.getIntParameterValue(CMQCFC.MQIACH_LISTENER_STATUS); + listenerStatusGauge.set( + status, + Attributes.of( + IBM_MQ_LISTENER_NAME, + listenerName, + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName())); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java new file mode 100644 index 000000000..6ed74dad9 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java @@ -0,0 +1,75 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.CMQXC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.time.Instant; + +public class MessageBuddy { + + private MessageBuddy() {} + + static String channelName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQCFC.MQCACH_CHANNEL_NAME).trim(); + } + + static String channelType(PCFMessage message) throws PCFException { + switch (message.getIntParameterValue(CMQCFC.MQIACH_CHANNEL_TYPE)) { + case CMQXC.MQCHT_SENDER: + return "sender"; + case CMQXC.MQCHT_SERVER: + return "server"; + case CMQXC.MQCHT_RECEIVER: + return "receiver"; + case CMQXC.MQCHT_REQUESTER: + return "requester"; + case CMQXC.MQCHT_SVRCONN: + return "server-connection"; + case CMQXC.MQCHT_CLNTCONN: + return "client-connection"; + case CMQXC.MQCHT_CLUSRCVR: + return "cluster-receiver"; + case CMQXC.MQCHT_CLUSSDR: + return "cluster-sender"; + case CMQXC.MQCHT_MQTT: + return "mqtt"; + case CMQXC.MQCHT_AMQP: + return "amqp"; + default: + throw new IllegalArgumentException( + "Unsupported channel type: " + + message.getIntParameterValue(CMQCFC.MQIACH_CHANNEL_TYPE)); + } + } + + static String topicName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQC.MQCA_TOPIC_STRING).trim(); + } + + public static String listenerName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQCFC.MQCACH_LISTENER_NAME).trim(); + } + + public static String queueName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQC.MQCA_Q_NAME).trim(); + } + + public static long channelStartTime(PCFMessage message) throws PCFException { + String date = message.getStringParameterValue(CMQCFC.MQCACH_CHANNEL_START_DATE).trim(); + String time = message.getStringParameterValue(CMQCFC.MQCACH_CHANNEL_START_TIME).trim(); + + Instant parsed = Instant.parse(date + "T" + time.replaceAll("\\.", ":") + "Z"); + return parsed.getEpochSecond(); + } + + public static String jobName(PCFMessage message) throws PCFException { + return message.getStringParameterValue(CMQCFC.MQCACH_MCA_JOB_NAME).trim(); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java new file mode 100644 index 000000000..531c217b0 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageFilter.java @@ -0,0 +1,77 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.ibm.mq.config.ExcludeFilters; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Helps to consolidate repeated exclude/filtering logic. */ +class MessageFilter { + + private static final Logger logger = LoggerFactory.getLogger(MessageFilter.class); + + private final String kind; + private final Collection filters; + private final ResourceExtractor extractor; + + private MessageFilter( + String kind, Collection filters, ResourceExtractor extractor) { + this.kind = kind; + this.filters = filters; + this.extractor = extractor; + } + + static MessageFilterBuilder ofKind(String kind) { + return new MessageFilterBuilder(kind); + } + + public List filter(List messages) throws PCFException { + List result = new ArrayList<>(); + for (PCFMessage message : messages) { + String resourceName = extractor.apply(message); + if (ExcludeFilters.isExcluded(resourceName, filters)) { + logger.debug("{} name = {} is excluded.", kind, resourceName); + } else { + result.add(message); + } + } + return result; + } + + static class MessageFilterBuilder { + + private final String kind; + private Set filters = new HashSet<>(); + + public MessageFilterBuilder(String kind) { + this.kind = kind; + } + + @CanIgnoreReturnValue + public MessageFilterBuilder excluding(Set filters) { + this.filters = new HashSet<>(filters); + return this; + } + + public MessageFilter withResourceExtractor(ResourceExtractor extractor) { + return new MessageFilter(kind, filters, extractor); + } + } + + interface ResourceExtractor { + // Ugh, exceptions everywhere, huh? + String apply(PCFMessage message) throws PCFException; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java new file mode 100644 index 000000000..dab85748e --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MetricsCollectorContext.java @@ -0,0 +1,101 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static java.util.Collections.emptyList; + +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.ExcludeFilters; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.annotation.concurrent.Immutable; +import org.jetbrains.annotations.NotNull; + +/** + * A temporary bundle to contain the collaborators of the original MetricsCollector base class until + * we can finish unwinding things. When done and there are no longer usages of MetricsCollector, we + * could consider renaming this. + */ +@Immutable +public final class MetricsCollectorContext { + + private final QueueManager queueManager; + private final PCFMessageAgent agent; + private final MQQueueManager mqQueueManager; + private final MetricsConfig metricsConfig; + + public MetricsCollectorContext( + QueueManager queueManager, + PCFMessageAgent agent, + MQQueueManager mqQueueManager, + MetricsConfig metricsConfig) { + this.queueManager = queueManager; + this.agent = agent; + this.mqQueueManager = mqQueueManager; + this.metricsConfig = metricsConfig; + } + + Set getChannelIncludeFilterNames() { + return queueManager.getChannelFilters().getInclude(); + } + + Set getChannelExcludeFilters() { + return queueManager.getChannelFilters().getExclude(); + } + + Set getListenerIncludeFilterNames() { + return queueManager.getListenerFilters().getInclude(); + } + + Set getListenerExcludeFilters() { + return queueManager.getListenerFilters().getExclude(); + } + + Set getTopicIncludeFilterNames() { + return queueManager.getTopicFilters().getInclude(); + } + + Set getTopicExcludeFilters() { + return queueManager.getTopicFilters().getExclude(); + } + + Set getQueueIncludeFilterNames() { + return queueManager.getQueueFilters().getInclude(); + } + + Set getQueueExcludeFilters() { + return queueManager.getQueueFilters().getExclude(); + } + + @NotNull + List send(PCFMessage request) throws IOException, MQDataException { + PCFMessage[] result = agent.send(request); + return result == null ? emptyList() : Arrays.asList(result); + } + + String getQueueManagerName() { + return queueManager.getName(); + } + + QueueManager getQueueManager() { + return queueManager; + } + + public MQQueueManager getMqQueueManager() { + return mqQueueManager; + } + + public MetricsConfig getMetricsConfig() { + return metricsConfig; + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java new file mode 100644 index 000000000..db1d4a254 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/PerformanceEventQueueCollector.java @@ -0,0 +1,131 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQGetMessageOptions; +import com.ibm.mq.MQMessage; +import com.ibm.mq.MQQueue; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Captures metrics from events logged to the queue manager performance event queue. +public final class PerformanceEventQueueCollector implements Consumer { + + private static final Logger logger = + LoggerFactory.getLogger(PerformanceEventQueueCollector.class); + private final LongCounter fullQueueDepthCounter; + private final LongCounter highQueueDepthCounter; + private final LongCounter lowQueueDepthCounter; + + public PerformanceEventQueueCollector(Meter meter) { + this.fullQueueDepthCounter = Metrics.createIbmMqQueueDepthFullEvent(meter); + this.highQueueDepthCounter = Metrics.createIbmMqQueueDepthHighEvent(meter); + this.lowQueueDepthCounter = Metrics.createIbmMqQueueDepthLowEvent(meter); + } + + private void readEvents(MetricsCollectorContext context, String performanceEventsQueueName) + throws Exception { + + MQQueue queue = null; + int counter = 0; + try { + int queueAccessOptions = MQConstants.MQOO_FAIL_IF_QUIESCING | MQConstants.MQOO_INPUT_SHARED; + queue = + context.getMqQueueManager().accessQueue(performanceEventsQueueName, queueAccessOptions); + // keep going until receiving the exception MQConstants.MQRC_NO_MSG_AVAILABLE + logger.debug("Start reading events from performance queue {}", performanceEventsQueueName); + while (true) { + try { + MQGetMessageOptions getOptions = new MQGetMessageOptions(); + getOptions.options = MQConstants.MQGMO_NO_WAIT | MQConstants.MQGMO_FAIL_IF_QUIESCING; + MQMessage message = new MQMessage(); + + queue.get(message, getOptions); + PCFMessage receivedMsg = new PCFMessage(message); + incrementCounterByEventType(context, receivedMsg); + counter++; + } catch (MQException e) { + if (e.reasonCode != MQConstants.MQRC_NO_MSG_AVAILABLE) { + logger.error(e.getMessage(), e); + } + break; + } catch (IOException e) { + logger.error(e.getMessage(), e); + break; + } + } + } finally { + if (queue != null) { + queue.close(); + } + } + logger.debug("Read {} events from performance queue {}", counter, performanceEventsQueueName); + } + + private void incrementCounterByEventType(MetricsCollectorContext context, PCFMessage receivedMsg) + throws PCFException { + String queueName = receivedMsg.getStringParameterValue(CMQC.MQCA_BASE_OBJECT_NAME).trim(); + Attributes attributes = + Attributes.of( + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName(), + MESSAGING_DESTINATION_NAME, + queueName); + switch (receivedMsg.getReason()) { + case CMQC.MQRC_Q_FULL: + if (context.getMetricsConfig().isIbmMqQueueDepthFullEventEnabled()) { + fullQueueDepthCounter.add(1, attributes); + } + break; + case CMQC.MQRC_Q_DEPTH_HIGH: + if (context.getMetricsConfig().isIbmMqQueueDepthHighEventEnabled()) { + highQueueDepthCounter.add(1, attributes); + } + break; + case CMQC.MQRC_Q_DEPTH_LOW: + if (context.getMetricsConfig().isIbmMqQueueDepthLowEventEnabled()) { + lowQueueDepthCounter.add(1, attributes); + } + break; + default: + logger.debug("Unknown event reason {}", receivedMsg.getReason()); + } + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + String performanceEventsQueueName = context.getQueueManager().getPerformanceEventsQueueName(); + logger.info( + "sending PCF agent request to read performance events from queue {}", + performanceEventsQueueName); + try { + readEvents(context, performanceEventsQueueName); + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting performance events for queue " + + performanceEventsQueueName, + e); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for performance events is {} milliseconds", exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java new file mode 100644 index 000000000..50187adeb --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddy.java @@ -0,0 +1,311 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static com.ibm.mq.constants.CMQC.MQQT_ALIAS; +import static com.ibm.mq.constants.CMQC.MQQT_CLUSTER; +import static com.ibm.mq.constants.CMQC.MQQT_LOCAL; +import static com.ibm.mq.constants.CMQC.MQQT_MODEL; +import static com.ibm.mq.constants.CMQC.MQQT_REMOTE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_TYPE; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; +import static io.opentelemetry.ibm.mq.metrics.Metrics.MIBY_TO_BYTES; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.MQCFIL; +import com.ibm.mq.headers.pcf.MQCFIN; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFParameter; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A collaborator buddy of the queue collectors that helps them to send a message, process the + * response, and generate metrics. + */ +final class QueueCollectionBuddy { + private static final Logger logger = LoggerFactory.getLogger(QueueCollectionBuddy.class); + private final Map gauges = new HashMap<>(); + + private final QueueCollectorSharedState sharedState; + private final LongGauge onqtimeShort; + private final LongGauge onqtimeLong; + + @FunctionalInterface + private interface AllowedGauge { + void set(MetricsCollectorContext context, Integer value, Attributes attributes); + } + + private static AllowedGauge createAllowedGauge( + LongGauge gauge, Function allowed) { + return createAllowedGauge(gauge, allowed, Integer::longValue /*identity*/); + } + + private static AllowedGauge createAllowedGauge( + LongGauge gauge, + Function allowed, + Function unitMangler) { + return (context, val, attributes) -> { + if (allowed.apply(context.getMetricsConfig())) { + gauge.set(unitMangler.apply(val), attributes); + } + }; + } + + QueueCollectionBuddy(Meter meter, QueueCollectorSharedState sharedState) { + this.sharedState = sharedState; + gauges.put( + CMQC.MQIA_CURRENT_Q_DEPTH, + createAllowedGauge( + Metrics.createIbmMqQueueDepth(meter), MetricsConfig::isIbmMqQueueDepthEnabled)); + gauges.put( + CMQC.MQIA_MAX_Q_DEPTH, + createAllowedGauge( + Metrics.createIbmMqMaxQueueDepth(meter), MetricsConfig::isIbmMqMaxQueueDepthEnabled)); + gauges.put( + CMQC.MQIA_OPEN_INPUT_COUNT, + createAllowedGauge( + Metrics.createIbmMqOpenInputCount(meter), MetricsConfig::isIbmMqOpenInputCountEnabled)); + gauges.put( + CMQC.MQIA_OPEN_OUTPUT_COUNT, + createAllowedGauge( + Metrics.createIbmMqOpenOutputCount(meter), + MetricsConfig::isIbmMqOpenOutputCountEnabled)); + gauges.put( + CMQC.MQIA_Q_SERVICE_INTERVAL, + createAllowedGauge( + Metrics.createIbmMqServiceInterval(meter), + MetricsConfig::isIbmMqServiceIntervalEnabled)); + gauges.put( + CMQC.MQIA_Q_SERVICE_INTERVAL_EVENT, + createAllowedGauge( + Metrics.createIbmMqServiceIntervalEvent(meter), + MetricsConfig::isIbmMqServiceIntervalEventEnabled)); + gauges.put( + CMQCFC.MQIACF_OLDEST_MSG_AGE, + createAllowedGauge( + Metrics.createIbmMqOldestMsgAge(meter), MetricsConfig::isIbmMqOldestMsgAgeEnabled)); + gauges.put( + CMQCFC.MQIACF_UNCOMMITTED_MSGS, + createAllowedGauge( + Metrics.createIbmMqUncommittedMessages(meter), + MetricsConfig::isIbmMqUncommittedMessagesEnabled)); + gauges.put( + CMQC.MQIA_MSG_DEQ_COUNT, + createAllowedGauge( + Metrics.createIbmMqMessageDeqCount(meter), + MetricsConfig::isIbmMqMessageDeqCountEnabled)); + gauges.put( + CMQC.MQIA_MSG_ENQ_COUNT, + createAllowedGauge( + Metrics.createIbmMqMessageEnqCount(meter), + MetricsConfig::isIbmMqMessageEnqCountEnabled)); + gauges.put( + CMQC.MQIA_HIGH_Q_DEPTH, + createAllowedGauge( + Metrics.createIbmMqHighQueueDepth(meter), MetricsConfig::isIbmMqHighQueueDepthEnabled)); + gauges.put( + CMQCFC.MQIACF_CUR_Q_FILE_SIZE, + createAllowedGauge( + Metrics.createIbmMqCurrentQueueFilesize(meter), + MetricsConfig::isIbmMqCurrentQueueFilesizeEnabled, + MIBY_TO_BYTES)); + gauges.put( + CMQCFC.MQIACF_CUR_MAX_FILE_SIZE, + createAllowedGauge( + Metrics.createIbmMqCurrentMaxQueueFilesize(meter), + MetricsConfig::isIbmMqCurrentMaxQueueFilesizeEnabled, + MIBY_TO_BYTES)); + + this.onqtimeShort = Metrics.createIbmMqOnqtimeShortPeriod(meter); + this.onqtimeLong = Metrics.createIbmMqOnqtimeLongPeriod(meter); + } + + /** + * Sends a PCFMessage request, reads the response, and generates metrics from the response. It + * handles all exceptions. + */ + void processPcfRequestAndPublishQMetrics( + MetricsCollectorContext context, PCFMessage request, String queueGenericName, int[] fields) { + try { + doProcessPcfRequestAndPublishQMetrics(context, request, queueGenericName, fields); + } catch (PCFException pcfe) { + logger.error( + "PCFException caught while collecting metric for Queue: {}", queueGenericName, pcfe); + if (pcfe.exceptionSource instanceof PCFMessage[]) { + PCFMessage[] msgs = (PCFMessage[]) pcfe.exceptionSource; + for (PCFMessage msg : msgs) { + logger.error(msg.toString()); + } + } + if (pcfe.exceptionSource instanceof PCFMessage) { + PCFMessage msg = (PCFMessage) pcfe.exceptionSource; + logger.error(msg.toString()); + } + // Don't throw exception as it will stop queue metric collection + } catch (Exception mqe) { + logger.error("MQException caught", mqe); + // Don't throw exception as it will stop queue metric collection + } + } + + private void doProcessPcfRequestAndPublishQMetrics( + MetricsCollectorContext context, PCFMessage request, String queueGenericName, int[] fields) + throws IOException, MQDataException { + logger.debug( + "sending PCF agent request to query metrics for generic queue {}", queueGenericName); + long startTime = System.currentTimeMillis(); + List response = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queue metrics query response for generic queue {} received in {} milliseconds", + queueGenericName, + endTime); + if (response.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + + List messages = + MessageFilter.ofKind("queue") + .excluding(context.getQueueExcludeFilters()) + .withResourceExtractor(MessageBuddy::queueName) + .filter(response); + + for (PCFMessage message : messages) { + handleMessage(context, message, fields); + } + } + + private void handleMessage(MetricsCollectorContext context, PCFMessage message, int[] fields) + throws PCFException { + String queueName = MessageBuddy.queueName(message); + String queueType = getQueueTypeFromName(message, queueName); + if (queueType == null) { + logger.info("Unable to determine queue type for queue name = {}", queueName); + return; + } + + logger.debug("Pulling out metrics for queue name {}", queueName); + getMetrics(context, message, queueName, queueType, fields); + } + + @Nullable + private String getQueueTypeFromName(PCFMessage message, String queueName) throws PCFException { + if (message.getParameterValue(CMQC.MQIA_Q_TYPE) == null) { + return sharedState.getType(queueName); + } + + String queueType = getQueueType(message); + sharedState.putQueueType(queueName, queueType); + return queueType; + } + + private static String getQueueType(PCFMessage message) throws PCFException { + String baseQueueType = getBaseQueueType(message); + return maybeAppendUsage(message, baseQueueType); + } + + private static String maybeAppendUsage(PCFMessage message, String baseQueueType) + throws PCFException { + if (message.getParameter(CMQC.MQIA_USAGE) == null) { + return baseQueueType; + } + switch (message.getIntParameterValue(CMQC.MQIA_USAGE)) { + case CMQC.MQUS_NORMAL: + return baseQueueType + "-normal"; + case CMQC.MQUS_TRANSMISSION: + return baseQueueType + "-transmission"; + default: + return baseQueueType; + } + } + + private static String getBaseQueueType(PCFMessage message) throws PCFException { + switch (message.getIntParameterValue(CMQC.MQIA_Q_TYPE)) { + case MQQT_LOCAL: + return "local"; + case MQQT_ALIAS: + return "alias"; + case MQQT_REMOTE: + return "remote"; + case MQQT_CLUSTER: + return "cluster"; + case MQQT_MODEL: + return "model"; + default: + logger.warn("Unknown type of queue {}", message.getIntParameterValue(CMQC.MQIA_Q_TYPE)); + return "unknown"; + } + } + + private void getMetrics( + MetricsCollectorContext context, + PCFMessage pcfMessage, + String queueName, + String queueType, + int[] fields) + throws PCFException { + + for (int field : fields) { + if (field == CMQC.MQCA_Q_NAME || field == CMQC.MQIA_USAGE || field == CMQC.MQIA_Q_TYPE) { + continue; + } + updateMetrics(context, pcfMessage, queueName, queueType, field); + } + } + + private void updateMetrics( + MetricsCollectorContext context, + PCFMessage pcfMessage, + String queueName, + String queueType, + int constantValue) + throws PCFException { + PCFParameter pcfParam = pcfMessage.getParameter(constantValue); + Attributes attributes = + Attributes.of( + MESSAGING_DESTINATION_NAME, + queueName, + IBM_MQ_QUEUE_TYPE, + queueType, + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName()); + + if (pcfParam instanceof MQCFIN) { + AllowedGauge g = this.gauges.get(constantValue); + if (g == null) { + throw new IllegalArgumentException("Unknown constantValue " + constantValue); + } + int metricVal = pcfMessage.getIntParameterValue(constantValue); + g.set(context, metricVal, attributes); + } + if (pcfParam instanceof MQCFIL) { + int[] metricVals = pcfMessage.getIntListParameterValue(constantValue); + if (context.getMetricsConfig().isIbmMqOnqtimeShortPeriodEnabled()) { + onqtimeShort.set(metricVals[0], attributes); + } + if (context.getMetricsConfig().isIbmMqOnqtimeLongPeriodEnabled()) { + onqtimeLong.set(metricVals[1], attributes); + } + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java new file mode 100644 index 000000000..94fcc7788 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectorSharedState.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.Nullable; + +final class QueueCollectorSharedState { + + private final ConcurrentHashMap queueNameToType = new ConcurrentHashMap<>(); + + QueueCollectorSharedState() {} + + public void putQueueType(String name, String value) { + queueNameToType.put(name, value); + } + + @Nullable + public String getType(String name) { + return queueNameToType.get(name); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java new file mode 100644 index 000000000..723e433cf --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerEventCollector.java @@ -0,0 +1,111 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.SERVICE_NAME; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.USER_NAME; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQGetMessageOptions; +import com.ibm.mq.MQMessage; +import com.ibm.mq.MQQueue; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +// Reads queue manager events and counts them as metrics +public final class QueueManagerEventCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(QueueManagerEventCollector.class); + private final LongCounter authorityEventCounter; + + public QueueManagerEventCollector(Meter meter) { + this.authorityEventCounter = Metrics.createIbmMqUnauthorizedEvent(meter); + } + + private void readEvents(MetricsCollectorContext context, String queueManagerEventsQueueName) + throws Exception { + + MQQueue queue = null; + try { + int queueAccessOptions = MQConstants.MQOO_FAIL_IF_QUIESCING | MQConstants.MQOO_INPUT_SHARED; + queue = + context.getMqQueueManager().accessQueue(queueManagerEventsQueueName, queueAccessOptions); + // keep going until receiving the exception MQConstants.MQRC_NO_MSG_AVAILABLE + while (true) { + try { + MQGetMessageOptions getOptions = new MQGetMessageOptions(); + getOptions.options = MQConstants.MQGMO_NO_WAIT | MQConstants.MQGMO_FAIL_IF_QUIESCING; + MQMessage message = new MQMessage(); + + queue.get(message, getOptions); + PCFMessage received = new PCFMessage(message); + if (received.getReason() == CMQC.MQRC_NOT_AUTHORIZED) { + + if (context.getMetricsConfig().isIbmMqUnauthorizedEventEnabled()) { + String username = received.getStringParameterValue(CMQCFC.MQCACF_USER_IDENTIFIER); + String applicationName = received.getStringParameterValue(CMQCFC.MQCACF_APPL_NAME); + authorityEventCounter.add( + 1, + Attributes.of( + IBM_MQ_QUEUE_MANAGER, + context.getQueueManagerName(), + USER_NAME, + username, + SERVICE_NAME, + applicationName)); + } + } else { + logger.debug("Unknown event reason {}", received.getReason()); + } + + } catch (MQException e) { + if (e.reasonCode != MQConstants.MQRC_NO_MSG_AVAILABLE) { + logger.error(e.getMessage(), e); + } + break; + } catch (IOException e) { + logger.error(e.getMessage(), e); + break; + } + } + } finally { + if (queue != null) { + queue.close(); + } + } + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + String queueManagerEventsQueueName = context.getQueueManager().getQueueManagerEventsQueueName(); + logger.info( + "sending PCF agent request to read queue manager events from queue {}", + queueManagerEventsQueueName); + try { + readEvents(context, queueManagerEventsQueueName); + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting queue manager events for queue " + + queueManagerEventsQueueName, + e); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for queue manager events is {} milliseconds", exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java new file mode 100644 index 000000000..2b3d57086 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollector.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; +import static io.opentelemetry.ibm.mq.metrics.Metrics.MIBY_TO_BYTES; + +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.util.List; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** This class is responsible for queue manager metric collection. */ +public final class QueueManagerMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(QueueManagerMetricsCollector.class); + + private final LongGauge statusGauge; + private final LongGauge connectionCountGauge; + private final LongGauge restartLogSizeGauge; + private final LongGauge reuseLogSizeGauge; + private final LongGauge archiveLogSizeGauge; + private final LongGauge maxActiveChannelsGauge; + + public QueueManagerMetricsCollector(Meter meter) { + this.statusGauge = Metrics.createIbmMqManagerStatus(meter); + this.connectionCountGauge = Metrics.createIbmMqConnectionCount(meter); + this.restartLogSizeGauge = Metrics.createIbmMqRestartLogSize(meter); + this.reuseLogSizeGauge = Metrics.createIbmMqReusableLogSize(meter); + this.archiveLogSizeGauge = Metrics.createIbmMqArchiveLogSize(meter); + this.maxActiveChannelsGauge = Metrics.createIbmMqManagerMaxActiveChannels(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + logger.debug( + "publishMetrics entry time for queuemanager {} is {} milliseconds", + context.getQueueManagerName(), + entryTime); + // CMQCFC.MQCMD_INQUIRE_Q_MGR_STATUS is 161 + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_MGR_STATUS); + // CMQCFC.MQIACF_Q_MGR_STATUS_ATTRS is 1229 + request.addParameter(CMQCFC.MQIACF_Q_MGR_STATUS_ATTRS, new int[] {CMQCFC.MQIACF_ALL}); + try { + // Note that agent.send() method is synchronized + logger.debug( + "sending PCF agent request to query queuemanager {}", context.getQueueManagerName()); + long startTime = System.currentTimeMillis(); + List responses = context.send(request); + long endTime = System.currentTimeMillis() - startTime; + logger.debug( + "PCF agent queuemanager metrics query response for {} received in {} milliseconds", + context.getQueueManagerName(), + endTime); + if (responses.isEmpty()) { + logger.debug("Unexpected error while PCFMessage.send(), response is empty"); + return; + } + Attributes attributes = Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManagerName()); + if (context.getMetricsConfig().isIbmMqManagerStatusEnabled()) { + int status = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_Q_MGR_STATUS); + statusGauge.set(status, attributes); + } + if (context.getMetricsConfig().isIbmMqConnectionCountEnabled()) { + int count = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_CONNECTION_COUNT); + connectionCountGauge.set(count, attributes); + } + if (context.getMetricsConfig().isIbmMqRestartLogSizeEnabled()) { + int logSize = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_RESTART_LOG_SIZE); + restartLogSizeGauge.set(MIBY_TO_BYTES.apply(logSize), attributes); + } + if (context.getMetricsConfig().isIbmMqReusableLogSizeEnabled()) { + int logSize = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_REUSABLE_LOG_SIZE); + reuseLogSizeGauge.set(MIBY_TO_BYTES.apply(logSize), attributes); + } + if (context.getMetricsConfig().isIbmMqArchiveLogSizeEnabled()) { + int logSize = responses.get(0).getIntParameterValue(CMQCFC.MQIACF_ARCHIVE_LOG_SIZE); + archiveLogSizeGauge.set(MIBY_TO_BYTES.apply(logSize), attributes); + } + if (context.getMetricsConfig().isIbmMqManagerMaxActiveChannelsEnabled()) { + int maxActiveChannels = context.getQueueManager().getMaxActiveChannels(); + maxActiveChannelsGauge.set(maxActiveChannels, attributes); + } + } catch (Exception e) { + logger.error(e.getMessage()); + throw new IllegalStateException(e); + } finally { + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug("Time taken to publish metrics for queuemanager is {} milliseconds", exitTime); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java new file mode 100644 index 000000000..35370fb73 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/QueueMetricsCollector.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class QueueMetricsCollector implements Consumer { + + private static final Logger logger = LoggerFactory.getLogger(QueueMetricsCollector.class); + + private final List> publishers = new ArrayList<>(); + private final InquireQCmdCollector inquireQueueCmd; + private final ExecutorService threadPool; + private final ConfigWrapper config; + + public QueueMetricsCollector(Meter meter, ExecutorService threadPool, ConfigWrapper config) { + this.threadPool = threadPool; + this.config = config; + QueueCollectionBuddy queueBuddy = + new QueueCollectionBuddy(meter, new QueueCollectorSharedState()); + this.inquireQueueCmd = new InquireQCmdCollector(queueBuddy); + publishers.add(new InquireQStatusCmdCollector(queueBuddy)); + publishers.add(new ResetQStatsCmdCollector(queueBuddy)); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting queue metrics..."); + + // first collect all queue types. + inquireQueueCmd.accept(context); + + // schedule all other jobs in parallel. + List> taskJobs = new ArrayList<>(); + for (Consumer p : publishers) { + taskJobs.add( + () -> { + p.accept(context); + return null; + }); + } + + try { + int timeout = this.config.getInt("queueMetricsCollectionTimeoutInSeconds", 20); + threadPool.invokeAll(taskJobs, timeout, TimeUnit.SECONDS); + } catch (InterruptedException e) { + logger.error("The thread was interrupted ", e); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java new file mode 100644 index 000000000..f7e56134d --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ReadConfigurationEventQueueCollector.java @@ -0,0 +1,142 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQGetMessageOptions; +import com.ibm.mq.MQMessage; +import com.ibm.mq.MQQueue; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.MQConstants; +import com.ibm.mq.headers.pcf.PCFMessage; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.metrics.Metrics; +import java.io.IOException; +import java.util.function.Consumer; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class ReadConfigurationEventQueueCollector + implements Consumer { + + private static final Logger logger = + LoggerFactory.getLogger(ReadConfigurationEventQueueCollector.class); + private final long bootTime; + private final LongGauge maxHandlesGauge; + + public ReadConfigurationEventQueueCollector(Meter meter) { + this.bootTime = System.currentTimeMillis(); + this.maxHandlesGauge = Metrics.createIbmMqManagerMaxHandles(meter); + } + + @Nullable + private PCFMessage findLastUpdate( + MetricsCollectorContext context, long entryTime, String configurationQueueName) + throws Exception { + // find the last update: + PCFMessage candidate = null; + + boolean consumeEvents = + context.getQueueManager().getConsumeConfigurationEventInterval() > 0 + && (entryTime - this.bootTime) + % context.getQueueManager().getConsumeConfigurationEventInterval() + == 0; + + MQQueue queue = null; + try { + int queueAccessOptions = MQConstants.MQOO_FAIL_IF_QUIESCING | MQConstants.MQOO_INPUT_SHARED; + if (!consumeEvents) { + // we are not consuming the events. + queueAccessOptions |= MQConstants.MQOO_BROWSE; + } + queue = context.getMqQueueManager().accessQueue(configurationQueueName, queueAccessOptions); + int maxSequenceNumber = 0; + // keep going until receiving the exception MQConstants.MQRC_NO_MSG_AVAILABLE + while (true) { + try { + MQGetMessageOptions getOptions = new MQGetMessageOptions(); + getOptions.options = MQConstants.MQGMO_NO_WAIT | MQConstants.MQGMO_FAIL_IF_QUIESCING; + if (!consumeEvents) { + getOptions.options |= MQConstants.MQGMO_BROWSE_NEXT; + } + MQMessage message = new MQMessage(); + + queue.get(message, getOptions); + PCFMessage received = new PCFMessage(message); + if (received.getMsgSeqNumber() > maxSequenceNumber) { + maxSequenceNumber = received.getMsgSeqNumber(); + candidate = received; + } + + } catch (MQException e) { + if (e.reasonCode != MQConstants.MQRC_NO_MSG_AVAILABLE) { + logger.error(e.getMessage(), e); + } + break; + } catch (IOException e) { + logger.error(e.getMessage(), e); + break; + } + } + } finally { + if (queue != null) { + queue.close(); + } + } + return candidate; + } + + @Override + public void accept(MetricsCollectorContext context) { + long entryTime = System.currentTimeMillis(); + String configurationQueueName = context.getQueueManager().getConfigurationQueueName(); + logger.info( + "sending PCF agent request to read configuration events from queue {}", + configurationQueueName); + try { + + PCFMessage candidate = findLastUpdate(context, entryTime, configurationQueueName); + + if (candidate == null) { + if (context.getQueueManager().isRefreshQueueManagerConfigurationEnabled()) { + // no event found. + // we issue a refresh request, which will generate a configuration event on the + // configuration event queue. + // note this may incur a performance cost to the queue manager. + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_REFRESH_Q_MGR); + request.addParameter(CMQCFC.MQIACF_REFRESH_TYPE, CMQCFC.MQRT_CONFIGURATION); + request.addParameter(CMQCFC.MQIACF_OBJECT_TYPE, CMQC.MQOT_Q_MGR); + context.send(request); + // try again: + candidate = findLastUpdate(context, entryTime, configurationQueueName); + } + } + + if (candidate != null) { + if (context.getMetricsConfig().isIbmMqManagerMaxHandlesEnabled()) { + int maxHandles = candidate.getIntParameterValue(CMQC.MQIA_MAX_HANDLES); + maxHandlesGauge.set( + maxHandles, Attributes.of(IBM_MQ_QUEUE_MANAGER, context.getQueueManager().getName())); + } + } + + } catch (Exception e) { + logger.error( + "Unexpected error occurred while collecting configuration events for queue " + + configurationQueueName, + e); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for configuration events is {} milliseconds", exitTime); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java new file mode 100644 index 000000000..6f75acfe2 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/ResetQStatsCmdCollector.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import java.util.Arrays; +import java.util.Set; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +final class ResetQStatsCmdCollector implements Consumer { + + static final int[] ATTRIBUTES = + new int[] {CMQC.MQIA_HIGH_Q_DEPTH, CMQC.MQIA_MSG_DEQ_COUNT, CMQC.MQIA_MSG_ENQ_COUNT}; + + private static final Logger logger = LoggerFactory.getLogger(ResetQStatsCmdCollector.class); + + static final String COMMAND = "MQCMD_RESET_Q_STATS"; + private final QueueCollectionBuddy queueBuddy; + + ResetQStatsCmdCollector(QueueCollectionBuddy queueBuddy) { + this.queueBuddy = queueBuddy; + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting metrics for command {}", COMMAND); + long entryTime = System.currentTimeMillis(); + + logger.debug( + "Attributes being sent along PCF agent request to query queue metrics: {} for command {}", + Arrays.toString(ATTRIBUTES), + COMMAND); + + Set queueGenericNames = context.getQueueIncludeFilterNames(); + for (String queueGenericName : queueGenericNames) { + // list of all metrics extracted through MQCMD_RESET_Q_STATS is mentioned here + // https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.ref.adm.doc/q088310_.htm + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_RESET_Q_STATS); + request.addParameter(CMQC.MQCA_Q_NAME, queueGenericName); + queueBuddy.processPcfRequestAndPublishQMetrics( + context, request, queueGenericName, ATTRIBUTES); + } + long exitTime = System.currentTimeMillis() - entryTime; + logger.debug( + "Time taken to publish metrics for all queues is {} milliseconds for command {}", + exitTime, + COMMAND); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java new file mode 100644 index 000000000..76ff2a957 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollector.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import io.opentelemetry.api.metrics.Meter; +import java.util.function.Consumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class TopicMetricsCollector implements Consumer { + private static final Logger logger = LoggerFactory.getLogger(TopicMetricsCollector.class); + private final InquireTStatusCmdCollector inquireTStatusCmdCollector; + + public TopicMetricsCollector(Meter meter) { + this.inquireTStatusCmdCollector = new InquireTStatusCmdCollector(meter); + } + + @Override + public void accept(MetricsCollectorContext context) { + logger.info("Collecting Topic metrics..."); + inquireTStatusCmdCollector.accept(context); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java new file mode 100644 index 000000000..68ede61c7 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Config.java @@ -0,0 +1,83 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import java.util.Map; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Utilities reading configuration and create domain objects */ +final class Config { + + private static final Logger logger = LoggerFactory.getLogger(Config.class); + + private Config() {} + + static void setUpSslConnection(Map config) { + getConfigValueAndSetSystemProperty(config, "keyStorePath", "javax.net.ssl.keyStore"); + getConfigValueAndSetSystemProperty( + config, "keyStorePassword", "javax.net.ssl.keyStorePassword"); + getConfigValueAndSetSystemProperty(config, "trustStorePath", "javax.net.ssl.trustStorePath"); + getConfigValueAndSetSystemProperty( + config, "trustStorePassword", "javax.net.ssl.trustStorePassword"); + } + + private static void getConfigValueAndSetSystemProperty( + Map otlpConfig, String configKey, String systemKey) { + Object configValue = otlpConfig.get(configKey); + if (configValue instanceof String && !((String) configValue).trim().isEmpty()) { + System.setProperty(systemKey, (String) configValue); + } + } + + static void configureSecurity(ConfigWrapper config) { + Map sslConnection = config.getSslConnection(); + if (sslConnection.isEmpty()) { + logger.debug( + "ssl truststore and keystore are not configured in config.yml, if SSL is enabled, pass them as jvm args"); + return; + } + + configureTrustStore(sslConnection); + configureKeyStore(sslConnection); + } + + private static void configureTrustStore(Map sslConnection) { + String trustStorePath = sslConnection.get("trustStorePath"); + if (trustStorePath == null || trustStorePath.isEmpty()) { + logger.debug( + "trustStorePath is not set in config.yml, ignoring setting trustStorePath as system property"); + return; + } + + System.setProperty("javax.net.ssl.trustStore", trustStorePath); + logger.debug("System property set for javax.net.ssl.trustStore is {}", trustStorePath); + + String trustStorePassword = sslConnection.get("trustStorePassword"); + + if (trustStorePassword != null && !trustStorePassword.isEmpty()) { + System.setProperty("javax.net.ssl.trustStorePassword", trustStorePassword); + logger.debug("System property set for javax.net.ssl.trustStorePassword is xxxxx"); + } + } + + private static void configureKeyStore(Map sslConnection) { + String keyStorePath = sslConnection.get("keyStorePath"); + if (keyStorePath == null || keyStorePath.isEmpty()) { + logger.debug( + "keyStorePath is not set in config.yml, ignoring setting keyStorePath as system property"); + return; + } + + System.setProperty("javax.net.ssl.keyStore", keyStorePath); + logger.debug("System property set for javax.net.ssl.keyStore is {}", keyStorePath); + String keyStorePassword = sslConnection.get("keyStorePassword"); + if (keyStorePassword != null && !keyStorePassword.isEmpty()) { + System.setProperty("javax.net.ssl.keyStorePassword", keyStorePassword); + logger.debug("System property set for javax.net.ssl.keyStorePassword is xxxxx"); + } + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java new file mode 100644 index 000000000..5f1d71e65 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapper.java @@ -0,0 +1,129 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import static java.util.Collections.emptyList; + +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.time.Duration; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import org.jetbrains.annotations.NotNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.yaml.snakeyaml.Yaml; + +/** Low-fi domain-specific yaml wrapper. */ +public final class ConfigWrapper { + + private static final Logger logger = LoggerFactory.getLogger(ConfigWrapper.class); + + private static final int DEFAULT_THREADS = 20; + private static final int DEFAULT_DELAY_SECONDS = 60; + private static final int DEFAULT_INITIAL_DELAY = 0; + + private final Map config; + + private ConfigWrapper(Map config) { + this.config = config; + } + + public static ConfigWrapper parse(String configFile) throws IOException { + Yaml yaml = new Yaml(); + Map config = + yaml.load(Files.newBufferedReader(Paths.get(configFile), Charset.defaultCharset())); + return new ConfigWrapper(config); + } + + public int getNumberOfThreads() { + int value = defaultedInt(getTaskSchedule(), "numberOfThreads", DEFAULT_THREADS); + if (value < DEFAULT_THREADS) { + logger.warn( + "numberOfThreads {} is less than the minimum number of threads allowed. Using {} instead.", + value, + DEFAULT_THREADS); + value = DEFAULT_THREADS; + } + return value; + } + + int getTaskDelaySeconds() { + return defaultedInt(getTaskSchedule(), "taskDelaySeconds", DEFAULT_DELAY_SECONDS); + } + + Duration getTaskDelay() { + return Duration.ofSeconds(getTaskDelaySeconds()); + } + + int getTaskInitialDelaySeconds() { + return defaultedInt(getTaskSchedule(), "initialDelaySeconds", DEFAULT_INITIAL_DELAY); + } + + @NotNull + @SuppressWarnings("unchecked") + List getQueueManagerNames() { + return getQueueManagers().stream() + .map(o -> (Map) o) + .map(x -> x.get("name")) + .collect(Collectors.toList()); + } + + @NotNull + @SuppressWarnings("unchecked") + public List> getQueueManagers() { + List> result = (List>) config.get("queueManagers"); + if (result == null) { + return emptyList(); + } + return result; + } + + @NotNull + @SuppressWarnings("unchecked") + public Map getSslConnection() { + Map result = (Map) config.get("sslConnection"); + if (result == null) { + return Collections.emptyMap(); + } + return result; + } + + public int getInt(String key, int defaultValue) { + Object result = config.get(key); + if (result == null) { + return defaultValue; + } + return (Integer) result; + } + + @NotNull + @SuppressWarnings("unchecked") + public Map getMetrics() { + Object metrics = config.get("metrics"); + if (!(metrics instanceof Map)) { + throw new IllegalArgumentException("config metrics section is missing"); + } + return (Map) metrics; + } + + private static int defaultedInt(Map section, String key, int defaultValue) { + Object val = section.get(key); + return val instanceof Integer ? (Integer) val : defaultValue; + } + + @SuppressWarnings("unchecked") + private Map getTaskSchedule() { + if (config.get("taskSchedule") instanceof Map) { + return (Map) config.get("taskSchedule"); + } + return Collections.emptyMap(); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java new file mode 100644 index 000000000..abd7e69fb --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/opentelemetry/Main.java @@ -0,0 +1,87 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.ibm.mq.WmqMonitor; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.resources.Resource; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import org.jetbrains.annotations.VisibleForTesting; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@SuppressWarnings("SystemOut") +public final class Main { + + private static final Logger logger = LoggerFactory.getLogger(Main.class); + + private Main() {} + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + System.err.println("Usage: Main "); + System.exit(1); + } + + try { + Class.forName("com.ibm.mq.headers.MQDataException"); + } catch (ClassNotFoundException e) { + System.err.println("IBM MQ jar is missing from classpath."); + System.exit(1); + } + + String configFile = args[0]; + + ConfigWrapper config = ConfigWrapper.parse(configFile); + + Thread.UncaughtExceptionHandler handler = + (t, e) -> logger.error("Unhandled exception in thread pool", e); + logger.debug("Initializing thread pool with {} threads", config.getNumberOfThreads()); + ScheduledExecutorService service = + Executors.newScheduledThreadPool( + config.getNumberOfThreads(), + r -> { + Thread thread = new Thread(r); + thread.setUncaughtExceptionHandler(handler); + return thread; + }); + + Config.configureSecurity(config); + Config.setUpSslConnection(config.getSslConnection()); + + run(config, service); + } + + public static void run(ConfigWrapper config, ScheduledExecutorService service) { + + AutoConfiguredOpenTelemetrySdk sdk = + AutoConfiguredOpenTelemetrySdk.builder() + .addMeterProviderCustomizer( + (builder, configProps) -> builder.setResource(Resource.empty())) + .build(); + + OpenTelemetrySdk otel = sdk.getOpenTelemetrySdk(); + + run(config, service, otel); + } + + @VisibleForTesting + public static void run( + ConfigWrapper config, ScheduledExecutorService service, OpenTelemetry otel) { + WmqMonitor monitor = new WmqMonitor(config, service, otel.getMeter("websphere/mq")); + ScheduledFuture unused = + service.scheduleAtFixedRate( + monitor::run, + config.getTaskInitialDelaySeconds(), + config.getTaskDelaySeconds(), + TimeUnit.SECONDS); + } +} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java new file mode 100644 index 000000000..cefa9e5d9 --- /dev/null +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java @@ -0,0 +1,78 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.util; + +import com.ibm.mq.MQException; +import com.ibm.mq.MQQueueManager; +import com.ibm.mq.headers.MQDataException; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.WmqContext; +import io.opentelemetry.ibm.mq.config.QueueManager; +import java.util.Hashtable; +import javax.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class WmqUtil { + + private static final Logger logger = LoggerFactory.getLogger(WmqUtil.class); + + private WmqUtil() {} + + public static PCFMessageAgent initPcfMessageAgent( + QueueManager queueManager, MQQueueManager ibmQueueManager) { + try { + PCFMessageAgent agent; + if (isNotNullOrEmpty(queueManager.getModelQueueName()) + && isNotNullOrEmpty(queueManager.getReplyQueuePrefix())) { + logger.debug("Initializing the PCF agent for model queue and reply queue prefix."); + agent = new PCFMessageAgent(); + agent.setModelQueueName(queueManager.getModelQueueName()); + agent.setReplyQueuePrefix(queueManager.getReplyQueuePrefix()); + logger.debug("Connecting to queueManager to set the modelQueueName and replyQueuePrefix."); + agent.connect(ibmQueueManager); + } else { + agent = new PCFMessageAgent(ibmQueueManager); + } + if (queueManager.getCcsid() != Integer.MIN_VALUE) { + agent.setCharacterSet(queueManager.getCcsid()); + } + + if (queueManager.getEncoding() != Integer.MIN_VALUE) { + agent.setEncoding(queueManager.getEncoding()); + } + logger.debug( + "Initialized PCFMessageAgent for queueManager {} in thread {}", + agent.getQManagerName(), + Thread.currentThread().getName()); + return agent; + } catch (MQDataException mqe) { + logger.error(mqe.getMessage(), mqe); + throw new IllegalStateException(mqe); + } + } + + @SuppressWarnings("rawtypes") + public static MQQueueManager connectToQueueManager(QueueManager queueManager) { + WmqContext auth = new WmqContext(queueManager); + Hashtable env = auth.getMqEnvironment(); + try { + MQQueueManager ibmQueueManager = new MQQueueManager(queueManager.getName(), env); + logger.debug( + "MQQueueManager connection initiated for queueManager {} in thread {}", + queueManager.getName(), + Thread.currentThread().getName()); + return ibmQueueManager; + } catch (MQException mqe) { + logger.error(mqe.getMessage(), mqe); + throw new IllegalStateException(mqe.getMessage()); + } + } + + private static boolean isNotNullOrEmpty(@Nullable String str) { + return str != null && !str.isEmpty(); + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java new file mode 100644 index 000000000..b4ae65e88 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ChannelMetricsCollectorTest.java @@ -0,0 +1,227 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static com.ibm.mq.constants.CMQC.MQRC_SELECTOR_ERROR; +import static com.ibm.mq.constants.CMQCFC.MQRCCF_CHL_STATUS_NOT_FOUND; +import static io.opentelemetry.ibm.mq.metricscollector.MetricAssert.assertThatMetric; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.params.provider.Arguments.arguments; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFException; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ChannelMetricsCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + ChannelMetricsCollector classUnderTest; + QueueManager queueManager; + MetricsCollectorContext context; + Meter meter; + @Mock PCFMessageAgent pcfMessageAgent; + + @BeforeEach + void setup() throws Exception { + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + void testPublishMetrics() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireChannelStatusCmd()); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + + List metricsList = + new ArrayList<>( + Arrays.asList( + "ibm.mq.message.count", + "ibm.mq.status", + "ibm.mq.byte.sent", + "ibm.mq.byte.received", + "ibm.mq.buffers.sent", + "ibm.mq.buffers.received")); + + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + if (metric.getName().equals("ibm.mq.message.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(17); + } + + if (metric.getName().equals("ibm.mq.status")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(3); + } + if (metric.getName().equals("ibm.mq.byte.sent")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(6984); + } + if (metric.getName().equals("ibm.mq.byte.received")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(5772); + } + if (metric.getName().equals("ibm.mq.buffers.sent")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(19); + } + if (metric.getName().equals("ibm.mq.buffers.received")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(20); + } + } + } + assertThat(metricsList).isEmpty(); + } + + /* + Request + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 42 (MQCMD_INQUIRE_CHANNEL_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 3] + MQCFST [type: 4, strucLength: 24, parameter: 3501 (MQCACH_FIRST/MQCACH_CHANNEL_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIN [type: 3, strucLength: 16, parameter: 1523 (MQIACH_CHANNEL_INSTANCE_TYPE), value: 1011] + MQCFIL [type: 5, strucLength: 48, parameter: 1524 (MQIACH_CHANNEL_INSTANCE_ATTRS), count: 8, values: {3501, 3506, 1527, 1534, 1538, 1535, 1539, 1536}] + + Response + PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 42 (MQCMD_INQUIRE_CHANNEL_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 11] + MQCFST [type: 4, strucLength: 40, parameter: 3501 (MQCACH_FIRST/MQCACH_CHANNEL_NAME), codedCharSetId: 819, stringLength: 20, string: DEV.ADMIN.SVRCONN ] + MQCFIN [type: 3, strucLength: 16, parameter: 1511 (MQIACH_CHANNEL_TYPE), value: 7] + MQCFIN [type: 3, strucLength: 16, parameter: 1539 (MQIACH_BUFFERS_RCVD/MQIACH_BUFFERS_RECEIVED), value: 20] + MQCFIN [type: 3, strucLength: 16, parameter: 1538 (MQIACH_BUFFERS_SENT), value: 19] + MQCFIN [type: 3, strucLength: 16, parameter: 1536 (MQIACH_BYTES_RCVD/MQIACH_BYTES_RECEIVED), value: 5772] + MQCFIN [type: 3, strucLength: 16, parameter: 1535 (MQIACH_BYTES_SENT), value: 6984] + MQCFST [type: 4, strucLength: 284, parameter: 3506 (MQCACH_CONNECTION_NAME), codedCharSetId: 819, stringLength: 264, string: 172.17.0.1] + MQCFIN [type: 3, strucLength: 16, parameter: 1523 (MQIACH_CHANNEL_INSTANCE_TYPE), value: 1011] + MQCFIN [type: 3, strucLength: 16, parameter: 1534 (MQIACH_MSGS), value: 17] + MQCFIN [type: 3, strucLength: 16, parameter: 1527 (MQIACH_CHANNEL_STATUS), value: 3] + MQCFIN [type: 3, strucLength: 16, parameter: 1609 (MQIACH_CHANNEL_SUBSTATE), value: 300] + */ + + private static PCFMessage[] createPCFResponseForInquireChannelStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS, 1, true); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "DEV.ADMIN.SVRCONN"); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, 7); + response1.addParameter(CMQCFC.MQIACH_BUFFERS_RECEIVED, 20); + response1.addParameter(CMQCFC.MQIACH_BUFFERS_SENT, 19); + response1.addParameter(CMQCFC.MQIACH_BYTES_RECEIVED, 5772); + response1.addParameter(CMQCFC.MQIACH_BYTES_SENT, 6984); + response1.addParameter(CMQCFC.MQCACH_CONNECTION_NAME, "172.17.0.1 "); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, 1011); + response1.addParameter(CMQCFC.MQIACH_MSGS, 17); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_STATUS, 3); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_SUBSTATE, 300); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_START_DATE, "2012-01-03"); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_START_TIME, "22.33.44"); + response1.addParameter(CMQCFC.MQCACH_MCA_JOB_NAME, "000042040000000C"); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS, 2, true); + response2.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "DEV.APP.SVRCONN"); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, 7); + response2.addParameter(CMQCFC.MQIACH_BUFFERS_RECEIVED, 20); + response2.addParameter(CMQCFC.MQIACH_BUFFERS_SENT, 19); + response2.addParameter(CMQCFC.MQIACH_BYTES_RECEIVED, 5772); + response2.addParameter(CMQCFC.MQIACH_BYTES_SENT, 6984); + response2.addParameter(CMQCFC.MQCACH_CONNECTION_NAME, "172.17.0.2 "); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, 1011); + response2.addParameter(CMQCFC.MQIACH_MSGS, 17); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_STATUS, 3); + response2.addParameter(CMQCFC.MQIACH_CHANNEL_SUBSTATE, 300); + response2.addParameter(CMQCFC.MQCACH_CHANNEL_START_DATE, "2012-01-04"); + response2.addParameter(CMQCFC.MQCACH_CHANNEL_START_TIME, "22.33.45"); + response2.addParameter(CMQCFC.MQCACH_MCA_JOB_NAME, "000042040000000D"); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL_STATUS, 2, true); + response3.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "TEST.APP.SVRCONN"); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, 7); + response3.addParameter(CMQCFC.MQIACH_BUFFERS_RECEIVED, 20); + response3.addParameter(CMQCFC.MQIACH_BUFFERS_SENT, 19); + response3.addParameter(CMQCFC.MQIACH_BYTES_RECEIVED, 5772); + response3.addParameter(CMQCFC.MQIACH_BYTES_SENT, 6984); + response3.addParameter(CMQCFC.MQCACH_CONNECTION_NAME, "172.17.0.2 "); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_INSTANCE_TYPE, 1011); + response3.addParameter(CMQCFC.MQIACH_MSGS, 17); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_STATUS, 3); + response3.addParameter(CMQCFC.MQIACH_CHANNEL_SUBSTATE, 300); + response3.addParameter(CMQCFC.MQCACH_CHANNEL_START_DATE, "2012-01-05"); + response3.addParameter(CMQCFC.MQCACH_CHANNEL_START_TIME, "22.33.46"); + response3.addParameter(CMQCFC.MQCACH_MCA_JOB_NAME, "000042040000000E"); + + return new PCFMessage[] {response1, response2, response3}; + } + + @Test + void testPublishMetrics_nullResponse() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))).thenReturn(null); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + assertThat(otelTesting.getMetrics()).isEmpty(); + } + + @Test + void testPublishMetrics_emptyResponse() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))).thenReturn(new PCFMessage[] {}); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + assertThat(otelTesting.getMetrics()).isEmpty(); + } + + @ParameterizedTest + @MethodSource("exceptionsToThrow") + void testPublishMetrics_pfException(Exception exceptionToThrow) throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))).thenThrow(exceptionToThrow); + classUnderTest = new ChannelMetricsCollector(meter); + + classUnderTest.accept(context); + + List exported = otelTesting.getMetrics(); + assertThat(exported.get(0).getLongGaugeData().getPoints()).hasSize(1); + assertThatMetric(exported.get(0), 0).hasName("ibm.mq.manager.active.channels").hasValue(0); + } + + static Stream exceptionsToThrow() { + return Stream.of( + arguments(new RuntimeException("KBAOOM")), + arguments(new PCFException(91, MQRCCF_CHL_STATUS_NOT_FOUND, "flimflam")), + arguments(new PCFException(4, MQRC_SELECTOR_ERROR, "shazbot")), + arguments(new PCFException(4, 42, "boz"))); + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java new file mode 100644 index 000000000..a0e06e336 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.constants.CMQXC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class InquireChannelCmdCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + InquireChannelCmdCollector classUnderTest; + + MetricsCollectorContext context; + Meter meter; + @Mock PCFMessageAgent pcfMessageAgent; + + @BeforeEach + public void setup() throws Exception { + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + QueueManager queueManager = + mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + public void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireChannelCmd()); + classUnderTest = new InquireChannelCmdCollector(meter); + classUnderTest.accept(context); + List metricsList = + new ArrayList<>( + Arrays.asList( + "ibm.mq.message.retry.count", + "ibm.mq.message.received.count", + "ibm.mq.message.sent.count")); + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + if (metric.getName().equals("ibm.mq.message.retry.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(22); + } + if (metric.getName().equals("ibm.mq.message.received.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(42); + } + if (metric.getName().equals("ibm.mq.message.sent.count")) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()) + .isEqualTo(64); + } + } + } + assertThat(metricsList).isEmpty(); + } + + private static PCFMessage[] createPCFResponseForInquireChannelCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_CHANNEL, 1, true); + response1.addParameter(CMQCFC.MQCACH_CHANNEL_NAME, "my.channel"); + response1.addParameter(CMQCFC.MQIACH_CHANNEL_TYPE, CMQXC.MQCHT_SVRCONN); + response1.addParameter(CMQCFC.MQIACH_MR_COUNT, 22); + response1.addParameter(CMQCFC.MQIACH_MSGS_RECEIVED, 42); + response1.addParameter(CMQCFC.MQIACH_MSGS_SENT, 64); + response1.addParameter(CMQCFC.MQIACH_MAX_INSTANCES, 3); + response1.addParameter(CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT, 3); + + return new PCFMessage[] {response1}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java new file mode 100644 index 000000000..6170e31f4 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java @@ -0,0 +1,102 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class ListenerMetricsCollectorTest { + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + ListenerMetricsCollector classUnderTest; + QueueManager queueManager; + ConfigWrapper config; + @Mock private PCFMessageAgent pcfMessageAgent; + + @BeforeEach + public void setup() throws Exception { + config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + } + + @Test + public void testPublishMetrics() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireListenerStatusCmd()); + + MetricsCollectorContext context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + classUnderTest = + new ListenerMetricsCollector( + otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq")); + classUnderTest.accept(context); + + MetricData metric = otelTesting.getMetrics().get(0); + assertThat(metric.getName()).isEqualTo("ibm.mq.listener.status"); + Set values = new HashSet<>(); + values.add(2L); + values.add(3L); + assertThat( + metric.getLongGaugeData().getPoints().stream() + .map(LongPointData::getValue) + .collect(Collectors.toSet())) + .isEqualTo(values); + } + + /* + Request + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 98 (MQCMD_INQUIRE_LISTENER_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 2] + MQCFST [type: 4, strucLength: 24, parameter: 3554 (MQCACH_LISTENER_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIL [type: 5, strucLength: 24, parameter: 1223 (MQIACF_LISTENER_STATUS_ATTRS), count: 2, values: {3554, 1599}] + + Response + PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 98 (MQCMD_INQUIRE_LISTENER_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 2] + MQCFST [type: 4, strucLength: 48, parameter: 3554 (MQCACH_LISTENER_NAME), codedCharSetId: 819, stringLength: 27, string: SYSTEM.DEFAULT.LISTENER.TCP] + MQCFIN [type: 3, strucLength: 16, parameter: 1599 (MQIACH_LISTENER_STATUS), value: 2] + */ + + private static PCFMessage[] createPCFResponseForInquireListenerStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS, 1, true); + response1.addParameter(CMQCFC.MQCACH_LISTENER_NAME, "DEV.DEFAULT.LISTENER.TCP"); + response1.addParameter(CMQCFC.MQIACH_LISTENER_STATUS, 2); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS, 2, true); + response2.addParameter(CMQCFC.MQCACH_LISTENER_NAME, "DEV.LISTENER.TCP"); + response2.addParameter(CMQCFC.MQIACH_LISTENER_STATUS, 3); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_LISTENER_STATUS, 3, true); + response3.addParameter(CMQCFC.MQCACH_LISTENER_NAME, "SYSTEM.LISTENER.TCP"); + response3.addParameter(CMQCFC.MQIACH_LISTENER_STATUS, 1); + + return new PCFMessage[] {response1, response2, response3}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java new file mode 100644 index 000000000..25d173d3d --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import org.assertj.core.api.Assertions; + +public class MetricAssert { + + private final MetricData metric; + private final int pointOffset; + + public MetricAssert(MetricData metric, int pointOffset) { + this.metric = metric; + this.pointOffset = pointOffset; + } + + static MetricAssert assertThatMetric(MetricData metric, int pointOffset) { + return new MetricAssert(metric, pointOffset); + } + + MetricAssert hasName(String name) { + Assertions.assertThat(metric.getName()).isEqualTo(name); + return this; + } + + MetricAssert hasValue(long value) { + Assertions.assertThat( + ((LongPointData) metric.getLongGaugeData().getPoints().toArray()[this.pointOffset]) + .getValue()) + .isEqualTo(value); + return this; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java new file mode 100644 index 000000000..bd0c1ef97 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java @@ -0,0 +1,339 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.MESSAGING_DESTINATION_NAME; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.metrics.Meter; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class QueueCollectionBuddyTest { + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + QueueCollectionBuddy classUnderTest; + QueueManager queueManager; + MetricsCollectorContext collectorContext; + Meter meter; + @Mock private PCFMessageAgent pcfMessageAgent; + + @BeforeEach + void setup() throws Exception { + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + collectorContext = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + void testProcessPcfRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + QueueCollectorSharedState sharedState = new QueueCollectorSharedState(); + sharedState.putQueueType("AMQ.5AF1608820C7D76E", "local-transmission"); + sharedState.putQueueType("DEV.DEAD.LETTER.QUEUE", "local-transmission"); + sharedState.putQueueType("DEV.QUEUE.1", "local-transmission"); + PCFMessage request = createPCFRequestForInquireQStatusCmd(); + when(pcfMessageAgent.send(request)).thenReturn(createPCFResponseForInquireQStatusCmd()); + + classUnderTest = new QueueCollectionBuddy(meter, sharedState); + classUnderTest.processPcfRequestAndPublishQMetrics( + collectorContext, request, "*", InquireQStatusCmdCollector.ATTRIBUTES); + + Map> expectedValues = + new HashMap<>( + ImmutableMap.of( + "DEV.DEAD.LETTER.QUEUE", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.oldest.msg.age", -1L, + "ibm.mq.uncommitted.messages", 0L, + "ibm.mq.onqtime.short_period", -1L, + "ibm.mq.onqtime.long_period", -1L, + "ibm.mq.queue.depth", 0L)), + "DEV.QUEUE.1", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.oldest.msg.age", -1L, + "ibm.mq.uncommitted.messages", 10L, + "ibm.mq.onqtime.short_period", -1L, + "ibm.mq.onqtime.long_period", -1L, + "ibm.mq.queue.depth", 1L)))); + + for (MetricData metric : otelTesting.getMetrics()) { + for (LongPointData d : metric.getLongGaugeData().getPoints()) { + String queueName = d.getAttributes().get(MESSAGING_DESTINATION_NAME); + Long expectedValue = expectedValues.get(queueName).remove(metric.getName()); + assertThat(d.getValue()).isEqualTo(expectedValue); + } + } + + for (Map metrics : expectedValues.values()) { + assertThat(metrics).isEmpty(); + } + } + + @Test + void testProcessPcfRequestAndPublishQMetricsForInquireQCmd() throws Exception { + PCFMessage request = createPCFRequestForInquireQCmd(); + when(pcfMessageAgent.send(request)).thenReturn(createPCFResponseForInquireQCmd()); + classUnderTest = new QueueCollectionBuddy(meter, new QueueCollectorSharedState()); + classUnderTest.processPcfRequestAndPublishQMetrics( + collectorContext, request, "*", InquireQCmdCollector.ATTRIBUTES); + + Map> expectedValues = + new HashMap<>( + ImmutableMap.of( + "DEV.DEAD.LETTER.QUEUE", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.queue.depth", 2L, + "ibm.mq.max.queue.depth", 5000L, + "ibm.mq.open.input.count", 2L, + "ibm.mq.open.output.count", 2L)), + "DEV.QUEUE.1", + new HashMap<>( + ImmutableMap.of( + "ibm.mq.queue.depth", 3L, + "ibm.mq.max.queue.depth", 5000L, + "ibm.mq.open.input.count", 3L, + "ibm.mq.open.output.count", 3L)))); + + for (MetricData metric : otelTesting.getMetrics()) { + for (LongPointData d : metric.getLongGaugeData().getPoints()) { + String queueName = d.getAttributes().get(MESSAGING_DESTINATION_NAME); + Long expectedValue = expectedValues.get(queueName).remove(metric.getName()); + assertThat(d.getValue()).isEqualTo(expectedValue); + } + } + + for (Map metrics : expectedValues.values()) { + assertThat(metrics).isEmpty(); + } + } + + @Test + void testProcessPcfRequestAndPublishQMetricsForResetQStatsCmd() throws Exception { + QueueCollectorSharedState sharedState = new QueueCollectorSharedState(); + sharedState.putQueueType("AMQ.5AF1608820C7D76E", "local-transmission"); + sharedState.putQueueType("DEV.DEAD.LETTER.QUEUE", "local-transmission"); + sharedState.putQueueType("DEV.QUEUE.1", "local-transmission"); + PCFMessage request = createPCFRequestForResetQStatsCmd(); + when(pcfMessageAgent.send(request)).thenReturn(createPCFResponseForResetQStatsCmd()); + classUnderTest = new QueueCollectionBuddy(meter, sharedState); + classUnderTest.processPcfRequestAndPublishQMetrics( + collectorContext, request, "*", ResetQStatsCmdCollector.ATTRIBUTES); + + for (MetricData metric : otelTesting.getMetrics()) { + Iterator iterator = metric.getLongGaugeData().getPoints().iterator(); + if (metric.getName().equals("ibm.mq.high.queue.depth")) { + assertThat(iterator.next().getValue()).isEqualTo(10); + } else if (metric.getName().equals("ibm.mq.message.deq.count")) { + assertThat(iterator.next().getValue()).isEqualTo(0); + } else if (metric.getName().equals("ibm.mq.message.enq.count")) { + assertThat(iterator.next().getValue()).isEqualTo(3); + } + } + } + + /* + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 2] + MQCFST [type: 4, strucLength: 24, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIL [type: 5, strucLength: 32, parameter: 1026 (MQIACF_Q_STATUS_ATTRS), count: 4, values: {2016, 1226, 1227, 1027}] + */ + private static PCFMessage createPCFRequestForInquireQStatusCmd() { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q_STATUS); + request.addParameter(CMQC.MQCA_Q_NAME, "*"); + request.addParameter(CMQCFC.MQIACF_Q_STATUS_ATTRS, new int[] {2016, 1226, 1227, 1027}); + return request; + } + + /* + 0 = {PCFMessage@6026} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 2, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 1, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: AMQ.5AF1608820C7D76E ] + MQCFIN [type: 3, strucLength: 16, parameter: 1103 (MQIACF_Q_STATUS_TYPE), value: 1105] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 12] + MQCFIN [type: 3, strucLength: 16, parameter: 1227 (MQIACF_OLDEST_MSG_AGE), value: -1] + MQCFIL [type: 5, strucLength: 24, parameter: 1226 (MQIACF_Q_TIME_INDICATOR), count: 2, values: {-1, -1}] + MQCFIN [type: 3, strucLength: 16, parameter: 1027 (MQIACF_UNCOMMITTED_MSGS), value: 0]" + + 1 = {PCFMessage@6029} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 2, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 2, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.DEAD.LETTER.QUEUE ] + MQCFIN [type: 3, strucLength: 16, parameter: 1103 (MQIACF_Q_STATUS_TYPE), value: 1105] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1227 (MQIACF_OLDEST_MSG_AGE), value: -1] + MQCFIL [type: 5, strucLength: 24, parameter: 1226 (MQIACF_Q_TIME_INDICATOR), count: 2, values: {-1, -1}] + MQCFIN [type: 3, strucLength: 16, parameter: 1027 (MQIACF_UNCOMMITTED_MSGS), value: 0]" + + 2 = {PCFMessage@6030} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 2, command: 41 (MQCMD_INQUIRE_Q_STATUS), msgSeqNumber: 3, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.QUEUE.1 ] + MQCFIN [type: 3, strucLength: 16, parameter: 1103 (MQIACF_Q_STATUS_TYPE), value: 1105] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 1227 (MQIACF_OLDEST_MSG_AGE), value: -1] + MQCFIL [type: 5, strucLength: 24, parameter: 1226 (MQIACF_Q_TIME_INDICATOR), count: 2, values: {-1, -1}] + MQCFIN [type: 3, strucLength: 16, parameter: 1027 (MQIACF_UNCOMMITTED_MSGS), value: 0]" + */ + private static PCFMessage[] createPCFResponseForInquireQStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_STATUS, 1, false); + response1.addParameter(CMQC.MQCA_Q_NAME, "AMQ.5AF1608820C7D76E"); + response1.addParameter(CMQCFC.MQIACF_Q_STATUS_TYPE, 1105); + response1.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 12); + response1.addParameter(CMQCFC.MQIACF_OLDEST_MSG_AGE, -1); + response1.addParameter(CMQCFC.MQIACF_Q_TIME_INDICATOR, new int[] {-1, -1}); + response1.addParameter(CMQCFC.MQIACF_UNCOMMITTED_MSGS, 0); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_STATUS, 2, false); + response2.addParameter(CMQC.MQCA_Q_NAME, "DEV.DEAD.LETTER.QUEUE"); + response2.addParameter(CMQCFC.MQIACF_Q_STATUS_TYPE, 1105); + response2.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 0); + response2.addParameter(CMQCFC.MQIACF_OLDEST_MSG_AGE, -1); + response2.addParameter(CMQCFC.MQIACF_Q_TIME_INDICATOR, new int[] {-1, -1}); + response2.addParameter(CMQCFC.MQIACF_UNCOMMITTED_MSGS, 0); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_STATUS, 1, false); + response3.addParameter(CMQC.MQCA_Q_NAME, "DEV.QUEUE.1"); + response3.addParameter(CMQCFC.MQIACF_Q_STATUS_TYPE, 1105); + response3.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 1); + response3.addParameter(CMQCFC.MQIACF_OLDEST_MSG_AGE, -1); + response3.addParameter(CMQCFC.MQIACF_Q_TIME_INDICATOR, new int[] {-1, -1}); + response3.addParameter(CMQCFC.MQIACF_UNCOMMITTED_MSGS, 10); + + return new PCFMessage[] {response1, response2, response3}; + } + + /* + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 3] + MQCFST [type: 4, strucLength: 24, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 0, stringLength: 1, string: *] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1001] + MQCFIL [type: 5, strucLength: 36, parameter: 1002 (MQIACF_Q_ATTRS), count: 5, values: {2016, 15, 3, 17, 18}] + */ + private static PCFMessage createPCFRequestForInquireQCmd() { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_INQUIRE_Q); + request.addParameter(CMQC.MQCA_Q_NAME, "*"); + request.addParameter(CMQC.MQIA_Q_TYPE, CMQC.MQQT_ALL); + request.addParameter(CMQCFC.MQIACF_Q_ATTRS, new int[] {2016, 15, 3, 17, 18}); + return request; + } + + /* + 0 = {PCFMessage@6059} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 1, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: AMQ.5AF1608820C76D80 ] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 17 (MQIA_OPEN_INPUT_COUNT), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 15 (MQIA_MAX_Q_DEPTH), value: 5000] + MQCFIN [type: 3, strucLength: 16, parameter: 18 (MQIA_OPEN_OUTPUT_COUNT), value: 1]" + + 1 = {PCFMessage@6060} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 2, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.DEAD.LETTER.QUEUE ] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 17 (MQIA_OPEN_INPUT_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 15 (MQIA_MAX_Q_DEPTH), value: 5000] + MQCFIN [type: 3, strucLength: 16, parameter: 18 (MQIA_OPEN_OUTPUT_COUNT), value: 0]" + + 2 = {PCFMessage@6061} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 13 (MQCMD_INQUIRE_Q), msgSeqNumber: 3, control: 0, compCode: 0, reason: 0, parameterCount: 6] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.QUEUE.1 ] + MQCFIN [type: 3, strucLength: 16, parameter: 20 (MQIA_Q_TYPE), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 3 (MQIA_CURRENT_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 17 (MQIA_OPEN_INPUT_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 15 (MQIA_MAX_Q_DEPTH), value: 5000] + MQCFIN [type: 3, strucLength: 16, parameter: 18 (MQIA_OPEN_OUTPUT_COUNT), value: 0]" + */ + + private static PCFMessage[] createPCFResponseForInquireQCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q, 1, false); + response1.addParameter(CMQC.MQCA_Q_NAME, "AMQ.5AF1608820C76D80"); + response1.addParameter(CMQC.MQIA_Q_TYPE, 1); + response1.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 1); + response1.addParameter(CMQC.MQIA_OPEN_INPUT_COUNT, 1); + response1.addParameter(CMQC.MQIA_MAX_Q_DEPTH, 5000); + response1.addParameter(CMQC.MQIA_OPEN_OUTPUT_COUNT, 1); + response1.addParameter(CMQC.MQIA_USAGE, CMQC.MQUS_NORMAL); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q, 2, false); + response2.addParameter(CMQC.MQCA_Q_NAME, "DEV.DEAD.LETTER.QUEUE"); + response2.addParameter(CMQC.MQIA_Q_TYPE, 1); + response2.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 2); + response2.addParameter(CMQC.MQIA_OPEN_INPUT_COUNT, 2); + response2.addParameter(CMQC.MQIA_MAX_Q_DEPTH, 5000); + response2.addParameter(CMQC.MQIA_OPEN_OUTPUT_COUNT, 2); + response2.addParameter(CMQC.MQIA_USAGE, CMQC.MQUS_TRANSMISSION); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q, 3, false); + response3.addParameter(CMQC.MQCA_Q_NAME, "DEV.QUEUE.1"); + response3.addParameter(CMQC.MQIA_Q_TYPE, 1); + response3.addParameter(CMQC.MQIA_CURRENT_Q_DEPTH, 3); + response3.addParameter(CMQC.MQIA_OPEN_INPUT_COUNT, 3); + response3.addParameter(CMQC.MQIA_MAX_Q_DEPTH, 5000); + response3.addParameter(CMQC.MQIA_OPEN_OUTPUT_COUNT, 3); + response3.addParameter(CMQC.MQIA_USAGE, CMQC.MQUS_TRANSMISSION); + + return new PCFMessage[] {response1, response2, response3}; + } + + /* + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 17 (MQCMD_RESET_Q_STATS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 1] + MQCFST [type: 4, strucLength: 24, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 0, stringLength: 1, string: *] + */ + private static PCFMessage createPCFRequestForResetQStatsCmd() { + PCFMessage request = new PCFMessage(CMQCFC.MQCMD_RESET_Q_STATS); + request.addParameter(CMQC.MQCA_Q_NAME, "*"); + return request; + } + + /* + 0 = {PCFMessage@6144} "PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 17 (MQCMD_RESET_Q_STATS), msgSeqNumber: 1, control: 0, compCode: 0, reason: 0, parameterCount: 5] + MQCFST [type: 4, strucLength: 68, parameter: 2016 (MQCA_Q_NAME), codedCharSetId: 819, stringLength: 48, string: DEV.DEAD.LETTER.QUEUE ] + MQCFIN [type: 3, strucLength: 16, parameter: 37 (MQIA_MSG_ENQ_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 38 (MQIA_MSG_DEQ_COUNT), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 36 (MQIA_HIGH_Q_DEPTH), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 35 (MQIA_TIME_SINCE_RESET), value: 65]" + */ + private static PCFMessage[] createPCFResponseForResetQStatsCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_RESET_Q_STATS, 1, false); + response1.addParameter(CMQC.MQCA_Q_NAME, "DEV.DEAD.LETTER.QUEUE"); + response1.addParameter(CMQC.MQIA_MSG_ENQ_COUNT, 3); + response1.addParameter(CMQC.MQIA_MSG_DEQ_COUNT, 0); + response1.addParameter(CMQC.MQIA_HIGH_Q_DEPTH, 10); + response1.addParameter(CMQC.MQIA_TIME_SINCE_RESET, 65); + + return new PCFMessage[] {response1}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java new file mode 100644 index 000000000..dde400b02 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java @@ -0,0 +1,121 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class QueueManagerMetricsCollectorTest { + + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + QueueManagerMetricsCollector classUnderTest; + QueueManager queueManager; + MetricsCollectorContext context; + @Mock PCFMessageAgent pcfMessageAgent; + + @BeforeEach + public void setup() throws Exception { + + ConfigWrapper config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + } + + @Test + public void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireQMgrStatusCmd()); + classUnderTest = + new QueueManagerMetricsCollector( + otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq")); + classUnderTest.accept(context); + List metricsList = new ArrayList<>(singletonList("ibm.mq.manager.status")); + + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + assertThat(metric.getLongGaugeData().getPoints().iterator().next().getValue()).isEqualTo(2); + } + } + assertThat(metricsList).isEmpty(); + } + + /* Request + PCFMessage: + MQCFH [type: 1, strucLength: 36, version: 1, command: 161 (MQCMD_INQUIRE_Q_MGR_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 1] + MQCFIL [type: 5, strucLength: 20, parameter: 1229 (MQIACF_Q_MGR_STATUS_ATTRS), count: 1, values: {1009}] + + Response + PCFMessage: + MQCFH [type: 2, strucLength: 36, version: 1, command: 161 (MQCMD_INQUIRE_Q_MGR_STATUS), msgSeqNumber: 1, control: 1, compCode: 0, reason: 0, parameterCount: 23] + MQCFST [type: 4, strucLength: 68, parameter: 2015 (MQCA_Q_MGR_NAME), codedCharSetId: 819, stringLength: 48, string: QM1 ] + MQCFIN [type: 3, strucLength: 16, parameter: 1149 (MQIACF_Q_MGR_STATUS), value: 2] + MQCFST [type: 4, strucLength: 20, parameter: 3208 (null), codedCharSetId: 819, stringLength: 0, string: ] + MQCFIN [type: 3, strucLength: 16, parameter: 1416 (null), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1232 (MQIACF_CHINIT_STATUS), value: 2] + MQCFIN [type: 3, strucLength: 16, parameter: 1233 (MQIACF_CMD_SERVER_STATUS), value: 2] + MQCFIN [type: 3, strucLength: 16, parameter: 1230 (MQIACF_CONNECTION_COUNT), value: 23] + MQCFST [type: 4, strucLength: 20, parameter: 3071 (MQCACF_CURRENT_LOG_EXTENT_NAME), codedCharSetId: 819, stringLength: 0, string: ] + MQCFST [type: 4, strucLength: 20, parameter: 2115 (null), codedCharSetId: 819, stringLength: 0, string: ] + MQCFST [type: 4, strucLength: 36, parameter: 2116 (null), codedCharSetId: 819, stringLength: 13, string: Installation1] + MQCFST [type: 4, strucLength: 28, parameter: 2117 (null), codedCharSetId: 819, stringLength: 8, string: /opt/mqm] + MQCFIN [type: 3, strucLength: 16, parameter: 1409 (null), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1420 (null), value: 9] + MQCFST [type: 4, strucLength: 44, parameter: 3074 (MQCACF_LOG_PATH), codedCharSetId: 819, stringLength: 24, string: /var/mqm/log/QM1/active/] + MQCFIN [type: 3, strucLength: 16, parameter: 1421 (null), value: 9] + MQCFST [type: 4, strucLength: 20, parameter: 3073 (MQCACF_MEDIA_LOG_EXTENT_NAME), codedCharSetId: 819, stringLength: 0, string: ] + MQCFIN [type: 3, strucLength: 16, parameter: 1417 (null), value: 0] + MQCFST [type: 4, strucLength: 20, parameter: 3072 (MQCACF_RESTART_LOG_EXTENT_NAME), codedCharSetId: 819, stringLength: 0, string: ] + MQCFIN [type: 3, strucLength: 16, parameter: 1418 (null), value: 1] + MQCFIN [type: 3, strucLength: 16, parameter: 1419 (null), value: 0] + MQCFIN [type: 3, strucLength: 16, parameter: 1325 (null), value: 0] + MQCFST [type: 4, strucLength: 32, parameter: 3175 (null), codedCharSetId: 819, stringLength: 12, string: 2018-05-08 ] + MQCFST [type: 4, strucLength: 28, parameter: 3176 (null), codedCharSetId: 819, stringLength: 8, string: 08.32.08] + */ + + private static PCFMessage[] createPCFResponseForInquireQMgrStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_Q_MGR_STATUS, 1, true); + response1.addParameter(CMQC.MQCA_Q_MGR_NAME, "QM1"); + response1.addParameter(CMQCFC.MQIACF_Q_MGR_STATUS, 2); + response1.addParameter(CMQCFC.MQIACF_CHINIT_STATUS, 2); + response1.addParameter(CMQCFC.MQIACF_CMD_SERVER_STATUS, 2); + response1.addParameter(CMQCFC.MQIACF_CONNECTION_COUNT, 23); + response1.addParameter(CMQCFC.MQCACF_CURRENT_LOG_EXTENT_NAME, ""); + response1.addParameter(CMQCFC.MQCACF_LOG_PATH, "/var/mqm/log/QM1/active/"); + response1.addParameter(CMQCFC.MQCACF_MEDIA_LOG_EXTENT_NAME, ""); + response1.addParameter(CMQCFC.MQCACF_RESTART_LOG_EXTENT_NAME, ""); + response1.addParameter(CMQCFC.MQIACF_RESTART_LOG_SIZE, 42); + response1.addParameter(CMQCFC.MQIACF_REUSABLE_LOG_SIZE, 42); + response1.addParameter(CMQCFC.MQIACF_ARCHIVE_LOG_SIZE, 42); + + return new PCFMessage[] {response1}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java new file mode 100644 index 000000000..865501573 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java @@ -0,0 +1,113 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.metricscollector; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.constants.CMQC; +import com.ibm.mq.constants.CMQCFC; +import com.ibm.mq.headers.pcf.PCFMessage; +import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.MetricsConfig; +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import io.opentelemetry.sdk.metrics.data.LongPointData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.testing.junit5.OpenTelemetryExtension; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class TopicMetricsCollectorTest { + @RegisterExtension + static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); + + TopicMetricsCollector classUnderTest; + QueueManager queueManager; + ConfigWrapper config; + @Mock private PCFMessageAgent pcfMessageAgent; + + @BeforeEach + void setup() throws Exception { + config = ConfigWrapper.parse("src/test/resources/conf/config.yml"); + ObjectMapper mapper = new ObjectMapper(); + queueManager = mapper.convertValue(config.getQueueManagers().get(0), QueueManager.class); + } + + @Test + void testPublishMetrics() throws Exception { + MetricsCollectorContext context = + new MetricsCollectorContext(queueManager, pcfMessageAgent, null, new MetricsConfig(config)); + classUnderTest = + new TopicMetricsCollector(otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq")); + + when(pcfMessageAgent.send(any(PCFMessage.class))) + .thenReturn(createPCFResponseForInquireTopicStatusCmd()); + + classUnderTest.accept(context); + + List metricsList = + new ArrayList<>(Arrays.asList("ibm.mq.publish.count", "ibm.mq.subscription.count")); + + for (MetricData metric : otelTesting.getMetrics()) { + if (metricsList.remove(metric.getName())) { + if (metric.getName().equals("ibm.mq.publish.count")) { + Set values = new HashSet<>(); + values.add(2L); + values.add(3L); + assertThat( + metric.getLongGaugeData().getPoints().stream() + .map(LongPointData::getValue) + .collect(Collectors.toSet())) + .isEqualTo(values); + } + if (metric.getName().equals("ibm.mq.subscription.count")) { + Set values = new HashSet<>(); + values.add(3L); + values.add(4L); + assertThat( + metric.getLongGaugeData().getPoints().stream() + .map(LongPointData::getValue) + .collect(Collectors.toSet())) + .isEqualTo(values); + } + } + } + assertThat(metricsList).isEmpty(); + } + + private static PCFMessage[] createPCFResponseForInquireTopicStatusCmd() { + PCFMessage response1 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS, 1, false); + response1.addParameter(CMQC.MQCA_TOPIC_STRING, "test"); + response1.addParameter(CMQC.MQIA_PUB_COUNT, 2); + response1.addParameter(CMQC.MQIA_SUB_COUNT, 3); + + PCFMessage response2 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS, 2, false); + response2.addParameter(CMQC.MQCA_TOPIC_STRING, "dev"); + response2.addParameter(CMQC.MQIA_PUB_COUNT, 3); + response2.addParameter(CMQC.MQIA_SUB_COUNT, 4); + + PCFMessage response3 = new PCFMessage(2, CMQCFC.MQCMD_INQUIRE_TOPIC_STATUS, 3, false); + response3.addParameter(CMQC.MQCA_TOPIC_STRING, "system"); + response3.addParameter(CMQC.MQIA_PUB_COUNT, 5); + response3.addParameter(CMQC.MQIA_SUB_COUNT, 6); + + return new PCFMessage[] {response1, response2, response3}; + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java new file mode 100644 index 000000000..73aadc6a1 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigTest.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.google.common.collect.ImmutableMap; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConfigTest { + + private Properties systemProperties; + + @BeforeEach + public void cacheSystemProperties() { + systemProperties = new Properties(); + for (Map.Entry entry : System.getProperties().entrySet()) { + systemProperties.put(entry.getKey().toString(), entry.getValue().toString()); + } + } + + @Test + void testSSLConnection() { + Config.setUpSslConnection( + new HashMap( + ImmutableMap.of( + "keyStorePath", "foo", + "trustStorePath", "bar", + "keyStorePassword", "password", + "trustStorePassword", "password1"))); + + assertThat(System.getProperties().get("javax.net.ssl.keyStore")).isEqualTo("foo"); + assertThat(System.getProperties().get("javax.net.ssl.trustStorePath")).isEqualTo("bar"); + assertThat(System.getProperties().get("javax.net.ssl.keyStorePassword")).isEqualTo("password"); + assertThat(System.getProperties().get("javax.net.ssl.trustStorePassword")) + .isEqualTo("password1"); + } + + @AfterEach + public void resetSystemProperties() { + System.getProperties().clear(); + for (Map.Entry entry : systemProperties.entrySet()) { + System.setProperty(entry.getKey().toString(), entry.getValue().toString()); + } + } +} diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java new file mode 100644 index 000000000..3d2397cc0 --- /dev/null +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/opentelemetry/ConfigWrapperTest.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.ibm.mq.opentelemetry; + +import static java.util.Collections.singletonList; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +import java.time.Duration; +import java.time.temporal.ChronoUnit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ConfigWrapperTest { + + String file; + + @BeforeEach + void setUp() { + file = ConfigWrapperTest.class.getResource("/conf/config.yml").getFile(); + // Windows resources can contain a colon, which can't be mapped to a Path cleanly + // They look like /D:/a/path/to/whatever + file = file.replaceFirst("^/([A-Z]:)/", "$1/"); + } + + @Test + void testQueueManagerNames() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getQueueManagerNames()).isEqualTo(singletonList("QM1")); + } + + @Test + void testNumberOfThreads() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getNumberOfThreads()).isEqualTo(20); + } + + @Test + void testTaskDelay() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getTaskDelay()).isEqualTo(Duration.of(27, ChronoUnit.SECONDS)); + } + + @Test + void testTaskInitialDelay() throws Exception { + ConfigWrapper config = ConfigWrapper.parse(file); + assertThat(config.getTaskInitialDelaySeconds()).isEqualTo(0); + } +} diff --git a/ibm-mq-metrics/src/test/resources/conf/config.yml b/ibm-mq-metrics/src/test/resources/conf/config.yml new file mode 100644 index 000000000..51a11d53f --- /dev/null +++ b/ibm-mq-metrics/src/test/resources/conf/config.yml @@ -0,0 +1,217 @@ +#This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. +#No need to change the default unless you know what you are doing. +#queueMetricsCollectionTimeoutInSeconds: 40 +#channelMetricsCollectionTimeoutInSeconds: 40 +#topicMetricsCollectionTimeoutInSeconds: 40 + +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Client" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + channelName: "DEV.ADMIN.SVRCONN" + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + username: "app" + password: "passw0rd" + + #PCF requests are always sent to SYSTEM.ADMIN.COMMAND.QUEUE. The PCF responses to these requests are sent to the default reply-to queue called + #SYSTEM.DEFAULT.MODEL.QUEUE. However, you can override this behavior and send it to a temporary dynamic queue by changing the modelQueueName and replyQueuePrefix fields. + #For more details around this https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm & https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm + #modelQueueName: "" + #replyQueuePrefix: "" + + # Name of the temporary dynamic queue holding the configuration events. This queue contains information regarding the configuration of the queue manager, notable MaxChannels and MaxActiveChannels. + # If unset, the default queue name `SYSTEM.ADMIN.CONFIG.EVENT` is applied. + # Configuration events need to be enabled explicitly in the queue manager configuration. See https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=monitoring-configuration-events for reference. + #configurationQueueName: "SYSTEM.ADMIN.CONFIG.EVENT" + + # Interval in milliseconds at which the configuration events in the configuration queue can be consumed. + # By default, no events are consumed. + #consumeConfigurationEventInterval: 600000 # 10 minutes + + # Enable running a queue manager refresh request to reload its configuration and create a configuration event. + # This action is only executed if no configuration events are found when reading the configuration queue.name: + # By default, this action is disabled. + #refreshQueueManagerConfigurationEnabled: false + + #Sets the CCSID used in the message descriptor of request and response messages. The default value is MQC.MQCCSI_Q_MGR. + #To set this, please use the integer value. + #ccsid: + + #Sets the encoding used in the message descriptor of request and response messages. The default value is MQC.MQENC_NATIVE. + #To set this, please use the integer value. + #encoding: + + # IBM Cipher Suite e.g. "SSL_RSA_WITH_AES_128_CBC_SHA256".. + # For translation to IBM Cipher http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm + # A cipher working for IBM Cloud MQ and Temurin JDK 8 is TLS_AES_128_GCM_SHA256 + #cipherSuite: "" + + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM", "TEST"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "EQUALS" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["system","$SYS"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + +#Run it as a scheduled task instead of running every minute. +#If you want to run this every minute, comment this out +taskSchedule: + numberOfThreads: 1 + taskDelaySeconds: 27 + + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: https://localhost:4318 + otel.exporter.otlp.protocol: http/protobuf + otel.metric.export.interval: 5s + otel.logs.exporter: none + otel.traces.exporter: none diff --git a/ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 b/ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 new file mode 100644 index 000000000..2fcbdd64b --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/IbmMqAttributes.java.j2 @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.ibm.mq.metrics; + +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import io.opentelemetry.api.common.AttributeKey; + +// This file is generated using weaver. Do not edit manually. + +/** Attribute definitions generated from a Weaver model. Do not edit manually. */ +public final class IbmMqAttributes { +{% for attr in ctx %} + /** + {{ attr.brief }} */{% if attr.type == 'string' %} + public final static AttributeKey {{ attr.name.upper().split('.')|join('_') }} = stringKey("{{attr.name}}"); + {% elif attr.type == 'int' %} + public final static AttributeKey {{ attr.name.upper().split('.')|join('_') }} = longKey("{{attr.name}}"); + {% else %} + // UNHANDLED TYPE PLEASE FIXME + public final static AttributeKey {{ attr.name.upper().split('.')|join('_') }} = ??key("{{attr.name}}"); + {% endif %} +{% endfor %} + private IbmMqAttributes(){} +} diff --git a/ibm-mq-metrics/templates/registry/java/Metrics.java.j2 b/ibm-mq-metrics/templates/registry/java/Metrics.java.j2 new file mode 100644 index 000000000..1b0097826 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/Metrics.java.j2 @@ -0,0 +1,40 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.api.metrics.LongCounter; +import io.opentelemetry.api.metrics.LongGauge; +import io.opentelemetry.api.metrics.Meter; +import java.util.function.Function; + +// This file is generated using weaver. Do not edit manually. + +/** Metric definitions generated from a Weaver model. Do not edit manually. */ +public final class Metrics { +public final static Function MIBY_TO_BYTES = x -> x * 1024L * 1024L; +private Metrics(){ +} +{% for metric in ctx %} + + {% if metric.instrument == "gauge" %} + public static LongGauge create{{ metric.metric_name.replace("_", ".")|split('.')|map('capitalize')|join }}(Meter meter) { + return meter + .gaugeBuilder("{{ metric.metric_name }}") + .ofLongs() + .setUnit("{{ metric.unit }}") + .setDescription("{{ metric.brief }}") + .build(); + } + {% elif metric.instrument == "counter" %} + public static LongCounter create{{ metric.metric_name.replace("_", ".")|split('.')|map('capitalize')|join }}(Meter meter) { + return meter + .counterBuilder("{{ metric.metric_name }}") + .setUnit("{{ metric.unit }}") + .setDescription("{{ metric.brief }}") + .build(); + } + {% endif %} +{% endfor %} +} diff --git a/ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 b/ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 new file mode 100644 index 000000000..df6fae562 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/MetricsConfig.java.j2 @@ -0,0 +1,36 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ +package io.opentelemetry.ibm.mq.metrics; + +import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; +import java.util.Map; + +// This file is generated using weaver. Do not edit manually. + +/** Configuration of metrics as defined in config.yml. */ +public final class MetricsConfig { + + private final Map config; + + public MetricsConfig(ConfigWrapper config) { + this.config = config.getMetrics(); + } +{% for metric in ctx %} + public boolean is{{ metric.metric_name.replace("_", ".")|split('.')|map('capitalize')|join }}Enabled() { + return isEnabled("{{ metric.metric_name }}"); + } +{% endfor %} + private boolean isEnabled(String key) { + Object metricInfo = config.get(key); + if (!(metricInfo instanceof Map)) { + return false; + } + Object enabled = ((Map) metricInfo).get("enabled"); + if (enabled instanceof Boolean) { + return (Boolean) enabled; + } + return false; + } +} diff --git a/ibm-mq-metrics/templates/registry/java/weaver.yaml b/ibm-mq-metrics/templates/registry/java/weaver.yaml new file mode 100644 index 000000000..ece71c233 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/java/weaver.yaml @@ -0,0 +1,10 @@ +templates: + - template: Metrics.java.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single + - template: MetricsConfig.java.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single + - template: IbmMqAttributes.java.j2 + filter: '.groups | map(select(.type == "attribute_group")) | map(.attributes[])' + application_mode: single diff --git a/ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 new file mode 100644 index 000000000..9c0fea34e --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/attribute_macros.j2 @@ -0,0 +1,36 @@ +{% import 'examples_macros.j2' as examples %} +{% macro type(attribute) %}{%- if attribute.type is mapping %} +{%- if attribute.type.members[0].value is string %}string{%- endif %} +{%- if attribute.type.members[0].value is int %}int{%- endif %} +{%- if attribute.type.members[0].value is float %}double{%- endif %} +{%- elif attribute.type == "template[boolean]" %}boolean +{%- elif attribute.type == "template[int]" %}int +{%- elif attribute.type == "template[double]" %}double +{%- elif attribute.type == "template[string]" %}string +{%- elif attribute.type == "template[boolean[]]" %}boolean[] +{%- elif attribute.type == "template[int[]]" %}int[] +{%- elif attribute.type == "template[double[]]" %}double[] +{%- elif attribute.type == "template[string[]]" %}string[] +{%- else %}{{ attribute.type | trim }}{%- endif %}{% endmacro %} + +{% macro name(attribute) %}{%- if attribute.type is startingwith("template[") %}`{{ attribute.name }}.` +{%- else %}`{{ attribute.name }}`{%- endif %}{% endmacro %} + +{% macro find_lineage(attr_id, lineage) %}{% if attr_id in lineage %}{{lineage[attr_id].source_group}}{% endif %}{% endmacro %} + +{% macro name_with_link(attribute, attribute_registry_base_url, lineage_attributes) %}[{{name(attribute)}}]({{attribute_registry_base_url}}/{{ find_lineage(attribute.name, lineage_attributes) | split_id | list | reject("eq", "registry")| first | kebab_case }}.md){% endmacro %} + +{% macro display_name(group) %} +{%- if 'display_name' in group %}{{ group.display_name }} +{%- else %}{{ group.id | split_id | list | reject("eq", "registry") | join(" ") | title_case | acronym }} Attributes +{%- endif %}{% endmacro %} + +{% macro heading_link_fragments(title) %}{{ title | trim | lower | replace(" ", "-") | replace("(", "") | replace(")", "") | replace("/", "") | replace("\\", "") | replace(".", "") | replace("!", "") | replace("?", "") | replace("~", "") | replace("#", "")}}{% endmacro %} + +{% macro humanize(text) %} + {{- text.replace('_', ' ') -}} +{% endmacro %} + +{% macro sentence_case(text) %} + {{- text[:1].upper() + text[1:].lower() -}} +{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 b/ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 new file mode 100644 index 000000000..e55c288b3 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/attribute_namespace.md.j2 @@ -0,0 +1,51 @@ +{#- This template is rendered per top-level registry namespace. -#} +{#- It consists of two variables: -#} +{#- - id: The top-level namespace id. -#} +{#- - groups: A sequence of all attribute groups under this namespace. -#} +{#- This includes deprecated groups. -#} +{%- import 'stability.j2' as stability -%} +{%- import 'notes.j2' as notes -%} +{%- import 'enum_macros.j2' as enums -%} +{%- import 'attribute_macros.j2' as attrs -%} +{%- import 'examples_macros.j2' as examples -%} +{%- set my_file_name = ctx.id | lower | kebab_case ~ ".md" -%} +{{- template.set_file_name(my_file_name) -}} +{%- set groups = namespace(deprecated=[], non_deprecated=[]) -%} +{%- for group in ctx.groups | sort(attribute="id") -%} +{%- if group.id[-10:] == "deprecated" -%} +{%- set groups.deprecated = groups.deprecated + [group] -%} +{%- else -%} +{%- set groups.non_deprecated = groups.non_deprecated + [group] -%} +{%- endif -%} +{%- endfor -%} +{%- set attr_groups = groups.non_deprecated + groups.deprecated -%} + + + + +# {{ attrs.humanize(attrs.sentence_case(ctx.id)) | acronym }} + +{%- if attr_groups | length > 1 %} +{% for group in attr_groups %} +- [{{ attrs.display_name(group) }}](#{{ attrs.heading_link_fragments(attrs.display_name(group)) }}) +{%- endfor -%} +{%- endif %} +{% for group in attr_groups %} +## {{ attrs.display_name(group) }} + +{% if group.brief.endswith("\n") -%} +{{ group.brief }} +{% else -%} +{{ group.brief }} +{{"\n"}} +{%- endif -%} +| Attribute | Type | Description | Examples | Stability | +|---|---|---|---|---| +{%- for attribute in group.attributes | sort(attribute="name") %}{% set attr_anchor = attribute.name | kebab_case %} +| {{ attrs.name(attribute) }} | {{ attrs.type(attribute) }} | {{ attribute.brief | trim }}{{ notes.add({"note": attribute.note, "name": attribute.name}) }} | {{ examples.format(attribute) | trim }} | {{ stability.badge(attribute.stability, attribute.deprecated) | trim }} | +{%- endfor %} +{{ notes.render() }} +{%- for enum in group.attributes | sort(attribute="name") %} +{%- if enum.type is mapping -%}{{ enums.table(enum, notes) }}{% endif %} +{%- endfor -%} +{%- endfor -%} diff --git a/ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 b/ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 new file mode 100644 index 000000000..b40c54788 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/attribute_table.j2 @@ -0,0 +1,13 @@ +{% import 'requirement.j2' as requirement %} +{% import 'stability.j2' as stability %} +{% import 'notes.j2' as notes %} +{% import 'attribute_macros.j2' as attrs %} +{% import 'enum_macros.j2' as enums %} +{% import 'sampling_macros.j2' as sampling %} +{% import 'examples_macros.j2' as examples %} +{#- Macro for creating attribute table -#} +{% macro generate(attributes, tag_filter, attribute_registry_base_url, lineage_attributes) %}{% if (tag_filter | length == 0) %}{% set filtered_attributes = attributes %}{% else %}{% set filtered_attributes = attributes | selectattr("tag", "in", tag_filter) %}{% endif %}{% if filtered_attributes | length > 0 %}| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +{% for attribute in filtered_attributes | attribute_sort %}| {{ attrs.name(attribute) }} | {{ attrs.type(attribute) }} | {{ attribute.brief | trim }}{{ notes.add({"note": attribute.note, "name": attribute.name}) }} | {{ examples.format(attribute) | trim }} | {{ requirement.render({"level": attribute.requirement_level, "name": attribute.name}, notes) | trim }} | {{ stability.badge(attribute.stability, attribute.deprecated) | trim }} | +{% endfor %}{{ notes.render() }}{{ sampling.snippet(filtered_attributes, attribute_registry_base_url, lineage_attributes) }}{{ enums.tables(filtered_attributes | selectattr("type", "mapping"), notes) }} +{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 b/ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 new file mode 100644 index 000000000..b600f6dd7 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/body_field_table.j2 @@ -0,0 +1,18 @@ +{% import 'requirement.j2' as requirement %} +{% import 'stability.j2' as stability %} +{% import 'notes.j2' as notes %} +{% import 'enum_macros.j2' as enums %} +{% import 'examples_macros.j2' as examples %} +{% macro flatten(fields, ns, depth) %}{% if fields %}{% for f in fields | sort(attribute="id") %} +{% set ns.flat = [ns.flat, [{'field':f,'depth':depth}]] | flatten %}{% if f.fields %}{% set _= flatten(f.fields, ns, depth + 1) %}{% endif %} +{% endfor %}{% endif %}{% endmacro %} +{% macro field_name(field, depth) -%} +{%- set name= " " * 2 * depth ~ '`' ~ field.id ~ '`' -%} +{%- if (field.type == "map") or (field.type == "map[]") %}{{ name ~ ":"}}{% else -%} +{{ name }}{% endif %}{% endmacro %} +{#- Macro for creating body table -#} +{% macro generate(fields) %}{% if (fields | length > 0) %}{% set ns = namespace(flat=[])%}{% set _ = flatten(fields, ns, 0) %}| Body Field | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +{% for f in ns.flat %}| {{ field_name(f.field, f.depth) }} | {{ f.field.type }} | {{ f.field.brief | trim }}{{ notes.add({"note": f.field.note}) }} | {{ examples.format(f.field) | trim }} | {{ requirement.render({"level": f.field.requirement_level, "name": f.field.id}, notes) | trim }} | {{ stability.badge(f.field.stability, f.field.deprecated) | trim }} | +{% endfor %}{{ notes.render() }}{{ enums.field_tables(ns.flat | map(attribute="field") | selectattr("type", "eq", "enum"), notes) -}} +{%- endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 new file mode 100644 index 000000000..d20327e14 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/enum_macros.j2 @@ -0,0 +1,30 @@ +{% import 'stability.j2' as stability %} +{% macro filter(member) %}{% if (member.deprecated is none or member.deprecated == "") %}{{ "True" }}{% else %}{{ "False" }}{% endif %}{% endmacro %} +{% macro table(enum, notes) %} +--- + +`{{enum.name}}` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used. + +| Value | Description | Stability | +|---|---|---| +{% for espec in enum.type.members | sort(attribute='value') %} +{%- if filter(espec) == "True" -%} +| `{{ espec.value }}` | {{ (espec.brief or espec.id) | trim }}{{ notes.add({"note": espec.note}) }} | {{ stability.badge(espec.stability, espec.deprecated) }} | +{% endif %}{% endfor %}{{ notes.render() }}{% endmacro %} +{% macro tables(enums, notes) -%} +{% for enum in enums | sort(attribute="name") -%} +{{ table(enum, notes) -}} +{% endfor %}{% endmacro %} +{% macro field_table(enum, notes) %} +`{{enum.id}}` has the following list of well-known values. If one of them applies, then the respective value MUST be used; otherwise, a custom value MAY be used. + +| Value | Description | Stability | +|---|---|---| +{% for espec in enum.members | sort(attribute='value') %} +{%- if filter(espec) == "True" -%} +| `{{ espec.value }}` | {{ (espec.brief or espec.id) | trim }}{{ notes.add({"note": espec.note}) }} | {{ stability.badge(espec.stability, espec.deprecated) }} | +{% endif %}{% endfor %}{{ notes.render() }}{% endmacro %} +{% macro field_tables(enums, notes) -%} +{% for enum in enums | sort(attribute="id") -%} +{{ field_table(enum, notes) -}} +{% endfor %}{% endmacro %} \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/markdown/event_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/event_macros.j2 new file mode 100644 index 000000000..c6ef8ef6f --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/event_macros.j2 @@ -0,0 +1,16 @@ +{#- Macros for simplifying creating "Event" documentation. -#} +{% import 'stability.j2' as stability %} +{% import 'body_field_table.j2' as body_table %} +{% macro header(event) %}**Status:** {{ stability.badge(event.stability, event.deprecated) }} + +The event name MUST be `{{ event.name }}`. + +{{ event.brief | trim }} +{%if event.note %} +{{ event.note | trim }} +{% endif %} +{% endmacro %} +{% macro body(body) %}{% if body %}**Body fields:** + +{{ body_table.generate(body.fields) }} +{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 new file mode 100644 index 000000000..bbd840d67 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/examples_macros.j2 @@ -0,0 +1,17 @@ +{% macro print_examples(examples) %}{%- for e in examples %}{%if loop.first == false %}; {% endif %}`{{ e | trim }}`{%- endfor %}{% endmacro %} + +{% macro format(item) %}{%- if item.examples %} +{%- if "[]" in item.type and "template" not in item.type %} +{%- if item.examples is sequence %} +{%- if item.examples | select("sequence") | length == 0 %}`{{ item.examples | trim }}` +{%- else %}{{ print_examples(item.examples) }} +{%- endif %} +{%- else %}`[{{ item.examples | trim }}]` +{%- endif %} +{%- elif item.examples is sequence %}{{ print_examples(item.examples) }} +{%- else %}`{{ item.examples | trim }}` +{%- endif %}{%- elif item.type is mapping %} +{%- for e in item.type.members %}{% if loop.index0 < 3 %}{% if loop.first == false %}; {% endif %}`{{ e.value | trim }}`{% endif %}{%- endfor %} +{%- elif item.type == "enum" -%} +{%- for e in item.members %}{% if loop.index0 < 3 %}{% if loop.first == false %}; {% endif %}`{{ e.value | trim }}`{% endif %}{%- endfor %} +{%- endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 new file mode 100644 index 000000000..deae008dd --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/metric_macros.j2 @@ -0,0 +1,8 @@ +{% macro instrument(type) -%} +{%- if type == "gauge" %}Gauge +{% elif type == "counter" %}Counter +{% elif type == "updowncounter" %}UpDownCounter +{% elif type == "histogram" %}Histogram +{% else %}{{ type }} +{%- endif %} +{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/metric_table.j2 b/ibm-mq-metrics/templates/registry/markdown/metric_table.j2 new file mode 100644 index 000000000..ddbd191c6 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/metric_table.j2 @@ -0,0 +1,7 @@ +{% import 'stability.j2' as stability %} +{% import 'notes.j2' as notes %} +{% import 'metric_macros.j2' as metrics %} +{% macro generate(group) %}| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `{{ group.metric_name }}` | {{ metrics.instrument(group.instrument) | trim }} | `{{ group.unit }}` | {{ group.brief | trim }}{{ notes.add({"note": group.note}) }} | {{ stability.badge(group.stability, group.deprecated) | trim }} | +{{ notes.render() }}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 b/ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 new file mode 100644 index 000000000..0ff8153bc --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/metrics.md.j2 @@ -0,0 +1,14 @@ +{%- import 'attribute_table.j2' as at -%} +{%- import 'metric_table.j2' as mt -%} + +# Produced Metrics + +{% for metric in ctx %} +## Metric `{{metric.metric_name}}` + +{{ mt.generate(metric) }} + +### `{{metric.metric_name}}` Attributes + +{{ at.generate(metric.attributes, "", "", metric.lineage.attributes) }} +{% endfor %} \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/markdown/notes.j2 b/ibm-mq-metrics/templates/registry/markdown/notes.j2 new file mode 100644 index 000000000..a96b4270e --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/notes.j2 @@ -0,0 +1,9 @@ +{%- set ns = namespace(notes=[],index=0) -%} +{%- macro add(note) %}{% if note.note %}{% set ns.notes = [ns.notes, [note]] | flatten %} [{{ ns.notes | length + ns.index }}]{% endif %}{% endmacro %} +{%- macro add_with_limit(note) %}{% if note.note | length > 50 %}{% set ns.notes = [ns.notes, [note]] | flatten %} [{{ ns.notes | length + ns.index }}]{% elif note.note %} {{ note.note | trim }}{% endif %}{% endmacro %} +{% macro render() %}{% if ns.notes | length > 0 %} +{%- for note in ns.notes %} +{% if note.name %}**[{{ns.index+loop.index}}] `{{note.name}}`:** {{ note.note | trim }}{% else -%}**[{{ns.index+loop.index}}]:** {{ note.note | trim }} {%- endif -%} +{%- if not loop.last -%}{{"\n"}}{%- endif -%} +{% endfor %}{% set ns.index = ns.notes | length + ns.index %}{% set ns.notes = [] %} +{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/readme.md.j2 b/ibm-mq-metrics/templates/registry/markdown/readme.md.j2 new file mode 100644 index 000000000..8aea78a9f --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/readme.md.j2 @@ -0,0 +1,42 @@ +{{- template.set_file_name("README.md") -}} + + + + + +# Attribute registry + +The attributes registry is the place where attributes are defined. An attribute definition covers the following properties of an attribute: + +- the `id` (the fully qualified name) of the attribute +- the `type` of the attribute +- the `stability` of the attribute +- a `brief` description of the attribute and optionally a longer `note` +- example values + +Attributes defined in the registry can be used in different semantic conventions. Attributes should be included in this registry before they are used in semantic conventions. Semantic conventions may override all the properties of an attribute except for the `id`, `type` and `stability` in case it's required for a particular context. In addition, semantic conventions specify the requirement level of an attribute in the corresponding context. + +A definition of an attribute in the registry doesn't necessarily imply that the attribute is used in any of the semantic conventions. + +If applicable, application developers are encouraged to use existing attributes from this registry. See also [these recommendations][developers recommendations] regarding attribute selection and attribute naming for custom use cases. + +All registered attributes are listed by namespace in this registry. + +> [!WARNING] +> +> The following registry overview is a work in progress. +> +> Further attribute namespaces are currently being migrated and will appear in this registry soon. + +Currently, the following namespaces exist: + +{% for bundle in ctx %} +{%- set my_file_name = bundle.id | kebab_case ~ ".md" -%} +- [{{ bundle.id | title_case | acronym }}]({{ my_file_name }}) +{% endfor %} +[developers recommendations]: ../general/naming.md#recommendations-for-application-developers + diff --git a/ibm-mq-metrics/templates/registry/markdown/requirement.j2 b/ibm-mq-metrics/templates/registry/markdown/requirement.j2 new file mode 100644 index 000000000..1f5714de3 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/requirement.j2 @@ -0,0 +1,9 @@ +{% macro render(attr, notes) -%} +{%- if attr.level == "recommended" %}`Recommended` +{% elif attr.level == "required" %}`Required` +{% elif attr.level == "opt_in" %}`Opt-In` +{% elif attr.level.conditionally_required %}`Conditionally Required`{{ notes.add_with_limit({"note": attr.level.conditionally_required, "name": attr.name}) }} +{% elif attr.level.recommended %}`Recommended`{{ notes.add_with_limit({"note": attr.level.recommended, "name": attr.name}) }} +{% else %}{{ level }} +{%- endif %} +{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 new file mode 100644 index 000000000..f19680cbd --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/resource_macros.j2 @@ -0,0 +1,16 @@ +{#- Macros for simplifying creating "Resource" documentation. -#} +{% import 'stability.j2' as stability %} +{% macro real_stability(resource) %} +{% if resource.attributes | map(attribute='stability') | unique | length > 1 -%} +{{ stability.badge("mixed", "") }} +{%- else -%} +{{ stability.badge(resource.stability, resource.deprecated) }} +{%- endif %} +{% endmacro %} +{% macro header(resource) %} +**Status:** {{ real_stability(resource) | trim }} + +**type:** `{{ resource.name }}` + +**Description:** {{ resource.brief }} +{% endmacro %} \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 b/ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 new file mode 100644 index 000000000..07929e7d4 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/sampling_macros.j2 @@ -0,0 +1,7 @@ +{% import 'attribute_macros.j2' as attrs %} +{% macro snippet(attributes, attribute_registry_base_url, lineage_attributes) %}{% set sampling_attributes = attributes | selectattr("sampling_relevant", "true") %}{% if sampling_attributes | length > 0 %} +The following attributes can be important for making sampling decisions +and SHOULD be provided **at span creation time** (if provided at all): + +{% for attribute in sampling_attributes | sort(attribute="name") %}* {{ attrs.name_with_link(attribute, attribute_registry_base_url, lineage_attributes) }} +{% endfor %}{% endif %}{% endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 b/ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 new file mode 100644 index 000000000..6b225c811 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/snippet.md.j2 @@ -0,0 +1,32 @@ + + + + + +{%- import 'attribute_table.j2' as at -%} +{%- import 'metric_table.j2' as mt -%} +{%- import 'event_macros.j2' as event -%} +{%- import 'resource_macros.j2' as resource %} + +{% macro generate_event(group) -%} +{{ event.header(group) }}{{ generate_attributes(group) }}{{ event.body(group.body) }}{% endmacro -%} +{%- macro generate_resource(group) -%} +{{ resource.header(group) }}{{ generate_attributes(group) }}{% endmacro -%} +{%- macro generate_metric(group) -%} +{{ mt.generate(group) }} +{{ generate_attributes(group) }}{% endmacro -%} +{%- macro generate_attributes(group) -%} +{{ at.generate(group.attributes, tag_filter, attribute_registry_base_url, group.lineage.attributes) }}{% endmacro -%} + +{% if group.type == "event" -%} +{{ generate_event(group) -}} +{%- elif group.type == "resource" -%} +{{ generate_resource(group) }} +{%- elif group.type == "metric" -%} +{{ generate_metric(group) }} +{%- else -%} +{{ generate_attributes(group) -}} +{% endif -%} + + + diff --git a/ibm-mq-metrics/templates/registry/markdown/stability.j2 b/ibm-mq-metrics/templates/registry/markdown/stability.j2 new file mode 100644 index 000000000..cc9ed7bde --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/stability.j2 @@ -0,0 +1,11 @@ +{% macro badge(stability, deprecated) -%} +{%- if deprecated %}![Deprecated](https://img.shields.io/badge/-deprecated-red)
{{ deprecated.note | trim }} +{%- elif stability == "mixed" %}![Mixed](https://img.shields.io/badge/-mixed-yellow) +{%- elif stability == "stable" %}![Stable](https://img.shields.io/badge/-stable-lightgreen) +{%- elif stability == "release_candidate" %}![Release Candidate](https://img.shields.io/badge/-rc-mediumorchid) +{%- elif stability == "deprecated" %}![Deprecated](https://img.shields.io/badge/-deprecated-red) +{%- elif stability == "experimental" %}![Development](https://img.shields.io/badge/-development-blue) +{%- elif stability == "development" %}![Development](https://img.shields.io/badge/-development-blue) +{%- else %}{{ "Unknown stability." }} +{%- endif %} +{%- endmacro %} diff --git a/ibm-mq-metrics/templates/registry/markdown/weaver.yaml b/ibm-mq-metrics/templates/registry/markdown/weaver.yaml new file mode 100644 index 000000000..6a1dd65ad --- /dev/null +++ b/ibm-mq-metrics/templates/registry/markdown/weaver.yaml @@ -0,0 +1,4 @@ +templates: + - pattern: metrics.md.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single \ No newline at end of file diff --git a/ibm-mq-metrics/weaver.Dockerfile b/ibm-mq-metrics/weaver.Dockerfile new file mode 100644 index 000000000..b1698505d --- /dev/null +++ b/ibm-mq-metrics/weaver.Dockerfile @@ -0,0 +1,6 @@ +# DO NOT BUILD +# This file is just for tracking dependencies of the semantic convention build. +# Dependabot can keep this file up to date with latest containers. + +# Weaver is used to generate markdown docs, and enforce policies on the model and run integration tests. +FROM otel/weaver:v0.15.1 AS weaver \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index f83b17cd2..00f27fdc0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -46,6 +46,7 @@ include(":consistent-sampling") include(":dependencyManagement") include(":disk-buffering") include(":example") +include(":ibm-mq-metrics") include(":jfr-events") include(":jfr-connection") include(":jmx-metrics") From 31d32d54cfa296b56b99d28b07cd047c1023512e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 12:38:42 -0700 Subject: [PATCH 173/371] chore(deps): pin otel/weaver docker tag to 95c0aaa (#2104) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- ibm-mq-metrics/weaver.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibm-mq-metrics/weaver.Dockerfile b/ibm-mq-metrics/weaver.Dockerfile index b1698505d..2011b9116 100644 --- a/ibm-mq-metrics/weaver.Dockerfile +++ b/ibm-mq-metrics/weaver.Dockerfile @@ -3,4 +3,4 @@ # Dependabot can keep this file up to date with latest containers. # Weaver is used to generate markdown docs, and enforce policies on the model and run integration tests. -FROM otel/weaver:v0.15.1 AS weaver \ No newline at end of file +FROM otel/weaver:v0.15.1@sha256:95c0aaa493d84ac72a1188756bd46eec1ead8e82004e7778ff5779736be8d578 AS weaver \ No newline at end of file From 2adcd0651cb6341794bd390b825af46fca5abc4f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 14 Aug 2025 13:24:50 -0700 Subject: [PATCH 174/371] Fix Java auto-update (#2105) --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index b45b3df4d..e1bcdeead 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -187,6 +187,7 @@ '(?\\d+) # renovate: datasource=java-version', ], depNameTemplate: 'java', + extractVersionTemplate: '^(?\\d+)', }, ], } From e4093f49f6f3e571e127546bd3bf95e6da0746f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 14 Aug 2025 21:07:14 +0000 Subject: [PATCH 175/371] chore(deps): update dependency java to v24 (#2106) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2d7d0abcc..58ecd14f2 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -54,7 +54,7 @@ jobs: - 11 - 17 - 21 - - 23 # renovate: datasource=java-version + - 24 # renovate: datasource=java-version # macos-latest drops support for java 8 temurin. Run java 8 on macos-13. Run java 11, 17, 21 on macos-latest. exclude: - os: macos-latest @@ -66,7 +66,7 @@ jobs: - os: macos-13 test-java-version: 21 - os: macos-13 - test-java-version: 23 # renovate: datasource=java-version + test-java-version: 24 # renovate: datasource=java-version steps: - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 From 19f0689aa1eb58bbd9afbe8c6bfd93cc9c921c0e Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 15 Aug 2025 02:39:02 -0700 Subject: [PATCH 176/371] Revert to shadow 8.3.9 (#2107) --- .../contrib/jmxscraper/JmxScraperContainer.java | 6 +++--- .../io/opentelemetry/contrib/jmxscraper/JmxScraper.java | 2 +- settings.gradle.kts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java index 8d06c0d7a..d1148fc89 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxScraperContainer.java @@ -237,8 +237,8 @@ public void start() { } else { Path script = generateShellScript(cmd, config); - this.withCopyFileToContainer(MountableFile.forHostPath(script, 500), "/scraper.sh"); - this.withCommand("/scraper.sh"); + this.withCopyFileToContainer(MountableFile.forHostPath(script), "/scraper.sh"); + this.withCommand("bash", "/scraper.sh"); } logger().info("Starting scraper with command: " + String.join(" ", this.getCommandParts())); @@ -263,7 +263,7 @@ private Path generateShellScript(List cmd, Map config) { logger().info("Scraper executed with /scraper.sh shell script"); for (int i = 0; i < lines.size(); i++) { - logger().info("/scrapper.sh:{} {}", i, lines.get(i)); + logger().info("/scraper.sh:{} {}", i, lines.get(i)); } return script; } diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index 8bf9f46ce..85c47ba0e 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -95,7 +95,7 @@ public static void main(String[] args) { jmxScraper.start(); } } catch (ConfigurationException e) { - logger.log(Level.SEVERE, "invalid configuration ", e); + logger.log(Level.SEVERE, "invalid configuration: " + e.getMessage(), e); System.exit(1); } catch (InvalidArgumentException e) { logger.log(Level.SEVERE, e.getMessage(), e); diff --git a/settings.gradle.kts b/settings.gradle.kts index 00f27fdc0..82364bf2a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - id("com.gradleup.shadow") version "9.0.1" + id("com.gradleup.shadow") version "8.3.9" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("com.gradle.develocity") version "4.1" } From 9a2f28ed326c9eace8aa4e1a413cf7e3b83209e3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 15 Aug 2025 04:26:49 -0700 Subject: [PATCH 177/371] Use bom-aligned sdk-testing dependency (#2108) --- ibm-mq-metrics/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 00a2c1988..fdc03c370 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -40,10 +40,10 @@ dependencies { api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") api("org.slf4j:slf4j-api:2.0.7") testImplementation("com.google.guava:guava") - testImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.50.0") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("org.assertj:assertj-core:3.27.3") integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") - integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing:1.50.0") + integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.2.0") integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") From 4226f6c590e5c7564abb99c283fdf43fd5258ded Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 14:37:03 +0300 Subject: [PATCH 178/371] chore(deps): update actions/checkout action to v5 (#2103) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-issue-owners.yml | 2 +- .github/workflows/auto-spotless-apply.yml | 2 +- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/backport.yml | 2 +- .github/workflows/build.yml | 8 ++++---- .github/workflows/codeql.yml | 2 +- .github/workflows/fossa.yml | 2 +- .github/workflows/gradle-wrapper-validation.yml | 2 +- .../workflows/issue-management-feedback-label.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/prepare-patch-release.yml | 2 +- .github/workflows/prepare-release-branch.yml | 6 +++--- .github/workflows/release.yml | 14 +++++++------- .github/workflows/reusable-link-check.yml | 2 +- .github/workflows/reusable-markdown-lint.yml | 2 +- .github/workflows/reusable-misspell-check.yml | 2 +- .github/workflows/reusable-shell-script-check.yml | 2 +- .../workflows/reusable-workflow-notification.yml | 2 +- 19 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index ddeb2bcf9..232396d9c 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -17,7 +17,7 @@ jobs: if: startsWith(github.event.label.name, 'component:') steps: - name: Checkout repository - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install js-yaml run: npm install js-yaml diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 0e47c3512..36da59e0d 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -39,7 +39,7 @@ jobs: app-id: 1296620 private-key: ${{ secrets.OTELBOT_JAVA_CONTRIB_PRIVATE_KEY }} - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 if: steps.unzip-patch.outputs.exists == 'true' with: repository: "${{ github.event.workflow_run.head_repository.full_name }}" diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index 69e9763bb..f6b873119 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -16,7 +16,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index a82067c53..eeb3ce5e3 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -21,7 +21,7 @@ jobs: exit 1 fi - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # history is needed to run git cherry-pick below fetch-depth: 0 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 58ecd14f2..ceeed192c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,7 +23,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 @@ -68,7 +68,7 @@ jobs: - os: macos-13 test-java-version: 24 # renovate: datasource=java-version steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - id: setup-java-test name: Set up Java ${{ matrix.test-java-version }} for tests @@ -98,7 +98,7 @@ jobs: integration-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 @@ -157,7 +157,7 @@ jobs: - integration-test runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 8433a829a..ff94a4aa4 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -36,7 +36,7 @@ jobs: - language: java runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up Java 17 if: matrix.language == 'java' diff --git a/.github/workflows/fossa.yml b/.github/workflows/fossa.yml index ddc524464..4f2c7d5d6 100644 --- a/.github/workflows/fossa.yml +++ b/.github/workflows/fossa.yml @@ -12,7 +12,7 @@ jobs: fossa: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: fossas/fossa-action@3ebcea1862c6ffbd5cf1b4d0bd6b3fe7bd6f2cac # v1.7.0 with: diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 7d7c04a90..45e549ad0 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -15,6 +15,6 @@ jobs: gradle-wrapper-validation: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 diff --git a/.github/workflows/issue-management-feedback-label.yml b/.github/workflows/issue-management-feedback-label.yml index fb4a03757..49db5efb5 100644 --- a/.github/workflows/issue-management-feedback-label.yml +++ b/.github/workflows/issue-management-feedback-label.yml @@ -18,7 +18,7 @@ jobs: github.event.comment.user.login == github.event.issue.user.login runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Remove labels env: diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 5b627ecee..c41857dd7 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -19,7 +19,7 @@ jobs: # Needed for GitHub OIDC token if publish_results is true id-token: write steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: persist-credentials: false diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index a0a0134b1..724b7605b 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -15,7 +15,7 @@ jobs: analyze: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml index d923fc218..a6d2a64aa 100644 --- a/.github/workflows/prepare-patch-release.yml +++ b/.github/workflows/prepare-patch-release.yml @@ -11,7 +11,7 @@ jobs: contents: write # for Git to git push runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - run: | if [[ ! $GITHUB_REF_NAME =~ ^release/v[0-9]+\.[0-9]+\.x$ ]]; then diff --git a/.github/workflows/prepare-release-branch.yml b/.github/workflows/prepare-release-branch.yml index c6cc32de0..f1c5160d5 100644 --- a/.github/workflows/prepare-release-branch.yml +++ b/.github/workflows/prepare-release-branch.yml @@ -9,7 +9,7 @@ jobs: prereqs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Verify prerequisites run: | @@ -30,7 +30,7 @@ jobs: needs: - prereqs steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Create release branch run: | @@ -87,7 +87,7 @@ jobs: needs: - prereqs steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set environment variables run: | diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 77cf6b26a..6561441ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 @@ -32,7 +32,7 @@ jobs: integration-test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 @@ -68,7 +68,7 @@ jobs: exit 1 fi - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set environment variables run: | @@ -97,7 +97,7 @@ jobs: # check out main branch to verify there won't be problems with merging the change log # at the end of this workflow - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: main @@ -112,7 +112,7 @@ jobs: fi # back to the release branch - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: # tags are needed for the generate-release-contributors.sh script fetch-depth: 0 @@ -197,7 +197,7 @@ jobs: needs: - release steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Copy change log section from release branch env: @@ -206,7 +206,7 @@ jobs: sed -n "0,/^## Version $VERSION /d;/^## Version /q;p" CHANGELOG.md \ > /tmp/changelog-section.md - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: ref: main diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 961a078f8..da70758ff 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -10,7 +10,7 @@ jobs: link-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: fetch-depth: 0 # needed for merge-base below diff --git a/.github/workflows/reusable-markdown-lint.yml b/.github/workflows/reusable-markdown-lint.yml index 734bf64f1..83fc58586 100644 --- a/.github/workflows/reusable-markdown-lint.yml +++ b/.github/workflows/reusable-markdown-lint.yml @@ -10,7 +10,7 @@ jobs: markdown-lint-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Run markdownlint run: | diff --git a/.github/workflows/reusable-misspell-check.yml b/.github/workflows/reusable-misspell-check.yml index b1e266cdf..7e8ddaf77 100644 --- a/.github/workflows/reusable-misspell-check.yml +++ b/.github/workflows/reusable-misspell-check.yml @@ -10,7 +10,7 @@ jobs: misspell-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install misspell run: | diff --git a/.github/workflows/reusable-shell-script-check.yml b/.github/workflows/reusable-shell-script-check.yml index 4e5f51e0f..b2e403110 100644 --- a/.github/workflows/reusable-shell-script-check.yml +++ b/.github/workflows/reusable-shell-script-check.yml @@ -10,7 +10,7 @@ jobs: shell-script-check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install shell check run: wget -qO- "https://github.com/koalaman/shellcheck/releases/download/stable/shellcheck-stable.linux.x86_64.tar.xz" | tar -xJv diff --git a/.github/workflows/reusable-workflow-notification.yml b/.github/workflows/reusable-workflow-notification.yml index 701f90f5a..61e8d6267 100644 --- a/.github/workflows/reusable-workflow-notification.yml +++ b/.github/workflows/reusable-workflow-notification.yml @@ -19,7 +19,7 @@ jobs: issues: write runs-on: ubuntu-latest steps: - - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Open issue or add comment if issue already open env: From 54e054e9859909f2368c94da25b65f7bdfcb8b53 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 15 Aug 2025 11:39:06 +0000 Subject: [PATCH 179/371] fix(deps): update all patch versions (#2100) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../main/kotlin/otel.java-conventions.gradle.kts | 2 +- disk-buffering/build.gradle.kts | 2 +- ibm-mq-metrics/build.gradle.kts | 16 ++++++++-------- opamp-client/build.gradle.kts | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index cc1a7c650..17cc114df 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -138,7 +138,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) - implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.0")) + implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.1")) compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 946639c6f..8bae11733 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.gradleup.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.8" + id("com.squareup.wire") version "5.3.10" } description = "Exporter implementations that store signals on disk" diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index fdc03c370..3eeb876c5 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -32,23 +32,23 @@ dependencies { api("com.google.code.findbugs:jsr305:3.0.2") api("io.swagger:swagger-annotations:1.6.16") api("org.jetbrains:annotations:26.0.2") - api("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1") + api("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0") api("org.yaml:snakeyaml:2.4") - api("com.fasterxml.jackson.core:jackson-databind:2.19.0") + api("com.fasterxml.jackson.core:jackson-databind:2.19.2") api("io.opentelemetry:opentelemetry-sdk") api("io.opentelemetry:opentelemetry-exporter-otlp") api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") - api("org.slf4j:slf4j-api:2.0.7") + api("org.slf4j:slf4j-api:2.0.17") testImplementation("com.google.guava:guava") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("org.assertj:assertj-core:3.27.3") + integrationTestImplementation("org.assertj:assertj-core:3.27.4") integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.2.0") + integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.0") integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") - integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0") - ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1") { + integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4") + ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0") { artifact { name = "com.ibm.mq.allclient" extension = "jar" @@ -59,7 +59,7 @@ dependencies { tasks.shadowJar { dependencies { - exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.2.1")) + exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0")) } } diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 416f9d72f..528265db7 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import java.net.URL plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.8" + id("com.squareup.wire") version "5.3.10" } description = "Client implementation of the OpAMP spec." From ccda30ab118bc6ee73746f03632e83d0a742d0af Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Fri, 15 Aug 2025 12:53:52 -0700 Subject: [PATCH 180/371] [chore] add ibm mq metrics to the components dropdown (#2116) Co-authored-by: github-actions[bot] --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index d268352e1..63d5b270f 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -16,6 +16,7 @@ body: - disk-buffering - gcp-auth-extension - gcp-resources + - ibm-mq-metrics - jfr-connection - jfr-events - jmx-metrics diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index b2756c423..45edd560b 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -16,6 +16,7 @@ body: - disk-buffering - gcp-auth-extension - gcp-resources + - ibm-mq-metrics - jfr-connection - jfr-events - jmx-metrics From da9244c2cad91ed16ca4fdc9868d555b1370ff1d Mon Sep 17 00:00:00 2001 From: Matthew Ho <5099946+mcmho@users.noreply.github.com> Date: Fri, 15 Aug 2025 13:08:58 -0700 Subject: [PATCH 181/371] [jmx-scraper] Add jmx-scraper jar to release artifacts (#2085) --- .github/workflows/release.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6561441ba..7b7f9af7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -181,12 +181,18 @@ jobs: run: | cp jmx-metrics/build/libs/opentelemetry-jmx-metrics-$VERSION-alpha.jar opentelemetry-jmx-metrics.jar cp jmx-metrics/build/libs/opentelemetry-jmx-metrics-$VERSION-alpha.jar.asc opentelemetry-jmx-metrics.jar.asc + cp jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar opentelemetry-jmx-scraper.jar + cp jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar opentelemetry-jmx-scraper.jar.asc + gh release create --target $GITHUB_REF_NAME \ --title "Version $VERSION" \ --notes-file /tmp/release-notes.txt \ v$VERSION \ opentelemetry-jmx-metrics.jar \ - opentelemetry-jmx-metrics.jar.asc + opentelemetry-jmx-metrics.jar.asc \ + opentelemetry-jmx-scraper.jar \ + opentelemetry-jmx-scraper.jar.asc + echo "version=$VERSION" >> $GITHUB_OUTPUT From 2039bd6f33bcce44309b90b836b2fdbdd8463014 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 15 Aug 2025 13:56:48 -0700 Subject: [PATCH 182/371] Fix gradle caching (#2118) --- jmx-metrics/build.gradle.kts | 2 ++ jmx-scraper/build.gradle.kts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index b254fc1d8..fe42d6f33 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -72,7 +72,9 @@ tasks { withType().configureEach { dependsOn(shadowJar) + inputs.files(layout.files(shadowJar)) systemProperty("shadow.jar.path", shadowJar.get().archiveFile.get().asFile.absolutePath) + systemProperty("gradle.project.version", "${project.version}") } diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 9ef9a7277..e02ee1b07 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -64,14 +64,17 @@ tasks { withType().configureEach { dependsOn(shadowJar) + inputs.files(layout.files(shadowJar)) systemProperty("shadow.jar.path", shadowJar.get().archiveFile.get().asFile.absolutePath) val testAppTask = project("test-app").tasks.named("jar") dependsOn(testAppTask) + inputs.files(layout.files(testAppTask)) systemProperty("app.jar.path", testAppTask.get().archiveFile.get().asFile.absolutePath) val testWarTask = project("test-webapp").tasks.named("war") dependsOn(testWarTask) + inputs.files(layout.files(testWarTask)) systemProperty("app.war.path", testWarTask.get().archiveFile.get().asFile.absolutePath) systemProperty("gradle.project.version", "${project.version}") From 0e82c27f5e962d7b53363c353a953ab26cbd7cfc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 Aug 2025 11:02:51 -0700 Subject: [PATCH 183/371] chore(deps): update plugin com.gradleup.shadow to v9 (#2110) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker --- jmx-metrics/build.gradle.kts | 2 ++ jmx-scraper/build.gradle.kts | 2 ++ settings.gradle.kts | 2 +- 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index fe42d6f33..d5376e670 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -59,6 +59,8 @@ tasks { shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE // required for mergeServiceFiles() + manifest { attributes["Implementation-Version"] = project.version } diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index e02ee1b07..061fdd03e 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -51,6 +51,8 @@ tasks { shadowJar { mergeServiceFiles() + duplicatesStrategy = DuplicatesStrategy.INCLUDE // required for mergeServiceFiles() + manifest { attributes["Implementation-Version"] = project.version } diff --git a/settings.gradle.kts b/settings.gradle.kts index 82364bf2a..ba79c4693 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - id("com.gradleup.shadow") version "8.3.9" + id("com.gradleup.shadow") version "9.0.2" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("com.gradle.develocity") version "4.1" } From 93e9e96916f7af157fb5661b9442572b6ea896f0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 08:27:40 +0300 Subject: [PATCH 184/371] fix(deps): update junit-framework monorepo to v5.13.4 (#2130) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- ibm-mq-metrics/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 3eeb876c5..acdbaf109 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -42,11 +42,11 @@ dependencies { testImplementation("com.google.guava:guava") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("org.assertj:assertj-core:3.27.4") - integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2") + integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4") integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.0") integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") - integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2") + integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.4") integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4") ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0") { artifact { From 790908f05323da0ac59a098d1584c3c8fe111a59 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 17 Aug 2025 22:29:30 -0700 Subject: [PATCH 185/371] Want OpenTelemetry Renovate package updates right away (#2125) --- .github/renovate.json5 | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index e1bcdeead..bcf7f2e98 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -24,6 +24,11 @@ // avoids these Renovate PRs from trickling in throughout the week // (consolidating the review process) "matchUpdateTypes": ["minor", "major"], + "excludePackageNames": [ + // want OpenTelemetry package updates right away + 'io.opentelemetry:**', + 'io.opentelemetry.*:**', + ], "schedule": ["before 8am on Monday"] }, { From 846deb9b77c380b684201f24853276164d12e67a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 05:29:57 +0000 Subject: [PATCH 186/371] fix(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.7 (#2126) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- runtime-attach/runtime-attach-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-attach/runtime-attach-core/build.gradle.kts b/runtime-attach/runtime-attach-core/build.gradle.kts index 835454e6c..b5ade38c4 100644 --- a/runtime-attach/runtime-attach-core/build.gradle.kts +++ b/runtime-attach/runtime-attach-core/build.gradle.kts @@ -7,7 +7,7 @@ description = "To help in create an OpenTelemetry distro able to runtime attach otelJava.moduleName.set("io.opentelemetry.contrib.attach.core") dependencies { - implementation("net.bytebuddy:byte-buddy-agent:1.17.6") + implementation("net.bytebuddy:byte-buddy-agent:1.17.7") // Used by byte-buddy but not brought in as a transitive dependency. compileOnly("com.google.code.findbugs:annotations") From 78969b3085c1dd4caa338f563db0587a794b7c46 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 05:30:23 +0000 Subject: [PATCH 187/371] chore(deps): update otel/weaver docker tag to v0.17.1 (#2127) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- ibm-mq-metrics/weaver.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibm-mq-metrics/weaver.Dockerfile b/ibm-mq-metrics/weaver.Dockerfile index 2011b9116..2acaa8257 100644 --- a/ibm-mq-metrics/weaver.Dockerfile +++ b/ibm-mq-metrics/weaver.Dockerfile @@ -3,4 +3,4 @@ # Dependabot can keep this file up to date with latest containers. # Weaver is used to generate markdown docs, and enforce policies on the model and run integration tests. -FROM otel/weaver:v0.15.1@sha256:95c0aaa493d84ac72a1188756bd46eec1ead8e82004e7778ff5779736be8d578 AS weaver \ No newline at end of file +FROM otel/weaver:v0.17.1@sha256:32523b5e44fb44418786347e9f7dde187d8797adb6d57a2ee99c245346c3cdfe AS weaver \ No newline at end of file From 3f4efc76c30d736b338409922fd923dd333fbf65 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 05:31:09 +0000 Subject: [PATCH 188/371] fix(deps): update dependency io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha to v2.19.0-alpha (#2129) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 1df9b2a7c..3591d2f73 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.18.1-alpha" +val otelInstrumentationVersion = "2.19.0-alpha" val semconvVersion = "1.34.0" javaPlatform { From 6494b4ee8bbf9fad515968e06fe730e744f76bbf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 09:51:37 +0300 Subject: [PATCH 189/371] fix(deps): update dependency com.google.protobuf:protobuf-bom to v4.32.0 (#2128) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 3591d2f73..7b62e76e8 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.2")) - api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.31.1")) + api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.32.0")) api(enforcedPlatform("com.squareup.okhttp3:okhttp-bom:5.1.0")) constraints { From b50e1bae69c57f3649f359c27dd84e8668ea8288 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Mon, 18 Aug 2025 06:03:03 -0700 Subject: [PATCH 190/371] Add slf4j-simple to the shaded jar (#2120) Co-authored-by: github-actions[bot] --- ibm-mq-metrics/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index acdbaf109..7e3afe148 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -39,6 +39,7 @@ dependencies { api("io.opentelemetry:opentelemetry-exporter-otlp") api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") api("org.slf4j:slf4j-api:2.0.17") + implementation("org.slf4j:slf4j-simple:2.0.17") testImplementation("com.google.guava:guava") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("org.assertj:assertj-core:3.27.4") From ddcc70821acd916cc6ba744c20af68c2d95b7323 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 18 Aug 2025 17:31:17 +0200 Subject: [PATCH 191/371] jmx-scraper wildfly metrics doc link to instrumentation (#2089) --- jmx-scraper/README.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 044145a96..a2f98f5b1 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -63,20 +63,20 @@ If there is a need to override existing ready-to-use metrics or to keep control Supported values for `otel.jmx.target.system` and support for `otel.jmx.target.source` and links to the metrics definitions: -| `otel.jmx.target.system` | description | `legacy` | `instrumentation` | -|--------------------------|-----------------------|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `activemq` | Apache ActiveMQ | [`activemq.yaml`](src/main/resources/activemq.yaml) | | -| `cassandra` | Apache Cassandra | [`cassandra.yaml`](src/main/resources/cassandra.yaml) | | -| `hbase` | Apache HBase | [`hbase.yaml`](src/main/resources/hbase.yaml) | | -| `hadoop` | Apache Hadoop | [`hadoop.yaml`](src/main/resources/hadoop.yaml) | | -| `jetty` | Eclipse Jetty | [`jetty.yaml`](src/main/resources/jetty.yaml) | [`jetty.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jetty.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jetty.md)) | -| `jvm` | JVM runtime metrics | [`jvm.yaml`](src/main/resources/jvm.yaml) | [`jvm.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jvm.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jvm.md)) | -| `kafka` | Apache Kafka | [`kafka.yaml`](src/main/resources/kafka.yaml) | | -| `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | -| `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | -| `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | -| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | [`tomcat.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml) | -| `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | | +| `otel.jmx.target.system` | description | `legacy` | `instrumentation` | +|--------------------------|-----------------------|-----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `activemq` | Apache ActiveMQ | [`activemq.yaml`](src/main/resources/activemq.yaml) | | +| `cassandra` | Apache Cassandra | [`cassandra.yaml`](src/main/resources/cassandra.yaml) | | +| `hbase` | Apache HBase | [`hbase.yaml`](src/main/resources/hbase.yaml) | | +| `hadoop` | Apache Hadoop | [`hadoop.yaml`](src/main/resources/hadoop.yaml) | | +| `jetty` | Eclipse Jetty | [`jetty.yaml`](src/main/resources/jetty.yaml) | [`jetty.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jetty.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jetty.md)) | +| `jvm` | JVM runtime metrics | [`jvm.yaml`](src/main/resources/jvm.yaml) | [`jvm.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/jvm.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/jvm.md)) | +| `kafka` | Apache Kafka | [`kafka.yaml`](src/main/resources/kafka.yaml) | | +| `kafka-consumer` | Apache Kafka consumer | [`kafka-consumer.yaml`](src/main/resources/kafka-consumer.yaml) | | +| `kafka-producer` | Apache Kafka producer | [`kafka-producer.yaml`](src/main/resources/kafka-producer.yaml) | | +| `solr` | Apache Solr | [`solr.yaml`](src/main/resources/solr.yaml) | | +| `tomcat` | Apache Tomcat | [`tomcat.yaml`](src/main/resources/tomcat.yaml) | [`tomcat.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/tomcat.md)) | +| `wildfly` | Wildfly | [`wildfly.yaml`](src/main/resources/wildfly.yaml) | [`wildfly.yaml`](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/src/main/resources/jmx/rules/tomcat.yaml) ([doc](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/library/wildfly.md)) | The source of metrics definitions is controlled by `otel.jmx.target.source`: From 0f23b508cbbd09f0413b304eb83d0a5a8236c13d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 10:31:49 -0700 Subject: [PATCH 192/371] chore(config): migrate renovate config (#2131) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Trask Stalnaker --- .github/renovate.json5 | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index bcf7f2e98..df236f85e 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -10,26 +10,34 @@ ], prHourlyLimit: 5, // we have a large number of parallel runners labels: [ - 'dependencies' + 'dependencies', ], packageRules: [ { // reduces the number of Renovate PRs // (patch updates are typically non-breaking) - "groupName": "all patch versions", - "matchUpdateTypes": ["patch"], - "schedule": ["before 8am every weekday"] + groupName: 'all patch versions', + matchUpdateTypes: [ + 'patch', + ], + schedule: [ + 'before 8am every weekday', + ], }, { // avoids these Renovate PRs from trickling in throughout the week // (consolidating the review process) - "matchUpdateTypes": ["minor", "major"], - "excludePackageNames": [ - // want OpenTelemetry package updates right away - 'io.opentelemetry:**', - 'io.opentelemetry.*:**', + matchUpdateTypes: [ + 'minor', + 'major', + ], + schedule: [ + 'before 8am on Monday', + ], + matchPackageNames: [ + '!io.opentelemetry:**', + '!io.opentelemetry.*:**', ], - "schedule": ["before 8am on Monday"] }, { matchPackageNames: [ @@ -176,7 +184,7 @@ customType: 'regex', datasourceTemplate: 'npm', managerFilePatterns: [ - '.github/workflows/**' + '.github/workflows/**', ], matchStrings: [ 'npx (?[^@]+)@(?[^\\s]+)', @@ -186,7 +194,7 @@ customType: 'regex', datasourceTemplate: 'java-version', managerFilePatterns: [ - '.github/workflows/**' + '.github/workflows/**', ], matchStrings: [ '(?\\d+) # renovate: datasource=java-version', From b5a09d433efc4beb99d278d903fcbb9dd1c1f926 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 18 Aug 2025 20:26:38 -0700 Subject: [PATCH 193/371] fix(deps): update all patch versions (#2136) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- dependencyManagement/build.gradle.kts | 2 +- disk-buffering/build.gradle.kts | 2 +- opamp-client/build.gradle.kts | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index ff94a4aa4..f100c0c67 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Initialize CodeQL - uses: github/codeql-action/init@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 + uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 with: languages: ${{ matrix.language }} # using "latest" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 + uses: github/codeql-action/analyze@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c41857dd7..e31e80969 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@df559355d593797519d70b90fc8edd5db049e7a2 # v3.29.9 + uses: github/codeql-action/upload-sarif@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 with: sarif_file: results.sarif diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 7b62e76e8..58d3e04d7 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -45,7 +45,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.uber.nullaway:nullaway:0.12.8") + api("com.uber.nullaway:nullaway:0.12.9") api("org.assertj:assertj-core:3.27.4") api("org.awaitility:awaitility:4.3.0") api("org.bouncycastle:bcpkix-jdk15on:1.70") diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 8bae11733..b124c50ba 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -7,7 +7,7 @@ plugins { id("com.gradleup.shadow") id("me.champeau.jmh") version "0.7.3" id("ru.vyarus.animalsniffer") version "2.0.1" - id("com.squareup.wire") version "5.3.10" + id("com.squareup.wire") version "5.3.11" } description = "Exporter implementations that store signals on disk" diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 528265db7..c4400d944 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ import java.net.URL plugins { id("otel.java-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.10" + id("com.squareup.wire") version "5.3.11" } description = "Client implementation of the OpAMP spec." From 4a93ce8548ae0ca7a94700e22e566809ed6c2f9c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 18 Aug 2025 23:35:03 -0700 Subject: [PATCH 194/371] Update CodeQL configuration (#2132) --- .github/workflows/codeql.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index f100c0c67..b061a35d8 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -53,9 +53,9 @@ jobs: uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 with: languages: ${{ matrix.language }} - # using "latest" helps to keep up with the latest Kotlin support + # using "linked" helps to keep up with the latest Kotlin support # see https://github.com/github/codeql-action/issues/1555#issuecomment-1452228433 - tools: latest + tools: linked - name: Assemble if: matrix.language == 'java' From 4fb5169f14cc44a5d7f1b172553dc241c02c858d Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 19 Aug 2025 09:03:33 -0700 Subject: [PATCH 195/371] Use the generated code to drive the definition of the metric (#2121) Co-authored-by: github-actions[bot] --- .../src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java index 7eaa2d8c0..c0223df86 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java @@ -14,6 +14,7 @@ import io.opentelemetry.api.metrics.LongGauge; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.ibm.mq.config.QueueManager; +import io.opentelemetry.ibm.mq.metrics.Metrics; import io.opentelemetry.ibm.mq.metrics.MetricsConfig; import io.opentelemetry.ibm.mq.metricscollector.ChannelMetricsCollector; import io.opentelemetry.ibm.mq.metricscollector.InquireChannelCmdCollector; @@ -66,7 +67,7 @@ public WmqMonitor(ConfigWrapper config, ExecutorService threadPool, Meter meter) this.metricsConfig = new MetricsConfig(config); - this.heartbeatGauge = meter.gaugeBuilder("ibm.mq.heartbeat").setUnit("1").ofLongs().build(); + this.heartbeatGauge = Metrics.createIbmMqHeartbeat(meter); this.threadPool = threadPool; jobs.add(new QueueManagerMetricsCollector(meter)); From ad68129d986f6753773173d26197ff371144641a Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 19 Aug 2025 14:04:18 -0700 Subject: [PATCH 196/371] [ibm-mq-metrics] Connection errors counter (#2122) Co-authored-by: github-actions[bot] Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- ibm-mq-metrics/docs/metrics.md | 16 + ibm-mq-metrics/model/attributes.yaml | 6 + ibm-mq-metrics/model/metrics.yaml | 12 + .../tests/WMQMonitorIntegrationTest.java | 31 ++ .../resources/conf/test-bad-config.yml | 302 ++++++++++++++++++ .../io/opentelemetry/ibm/mq/WmqMonitor.java | 11 + .../ibm/mq/metrics/IbmMqAttributes.java | 3 + .../opentelemetry/ibm/mq/metrics/Metrics.java | 8 + .../ibm/mq/metrics/MetricsConfig.java | 4 + 9 files changed, 393 insertions(+) create mode 100644 ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml diff --git a/ibm-mq-metrics/docs/metrics.md b/ibm-mq-metrics/docs/metrics.md index 63a71e3f2..2cfe9047c 100644 --- a/ibm-mq-metrics/docs/metrics.md +++ b/ibm-mq-metrics/docs/metrics.md @@ -799,3 +799,19 @@ | Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | |---|---|---|---|---|---| | `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | + + + +## Metric `ibm.mq.connection.errors` + +| Name | Instrument Type | Unit (UCUM) | Description | Stability | +| -------- | --------------- | ----------- | -------------- | --------- | +| `ibm.mq.connection.errors` | Counter | `{errors}` | Number of connection errors | ![Development](https://img.shields.io/badge/-development-blue) | + + +### `ibm.mq.connection.errors` Attributes + +| Attribute | Type | Description | Examples | [Requirement Level](https://opentelemetry.io/docs/specs/semconv/general/attribute-requirement-level/) | Stability | +|---|---|---|---|---|---| +| `error.code` | string | The reason code associated with an error | `2038`; `2543`; `2009` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | +| `ibm.mq.queue.manager` | string | The name of the IBM queue manager | `MQ1` | `Required` | ![Development](https://img.shields.io/badge/-development-blue) | diff --git a/ibm-mq-metrics/model/attributes.yaml b/ibm-mq-metrics/model/attributes.yaml index ef7ffdafd..4e59f631b 100644 --- a/ibm-mq-metrics/model/attributes.yaml +++ b/ibm-mq-metrics/model/attributes.yaml @@ -73,3 +73,9 @@ groups: note: This is duplicated from otel semantic-conventions. stability: development examples: [ "Wordle", "JMSService" ] + - id: error.code + type: string + brief: > + The reason code associated with an error + stability: development + examples: [ "2038", "2543", "2009" ] diff --git a/ibm-mq-metrics/model/metrics.yaml b/ibm-mq-metrics/model/metrics.yaml index cdc20456a..9e4e72fee 100644 --- a/ibm-mq-metrics/model/metrics.yaml +++ b/ibm-mq-metrics/model/metrics.yaml @@ -609,3 +609,15 @@ groups: attributes: - ref: ibm.mq.queue.manager requirement_level: required + - id: ibm.mq.connection.errors + type: metric + metric_name: ibm.mq.connection.errors + stability: development + brief: "Number of connection errors" + instrument: counter + unit: "{errors}" + attributes: + - ref: ibm.mq.queue.manager + requirement_level: required + - ref: error.code + requirement_level: required diff --git a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java index 9f0a64f21..853a54d85 100644 --- a/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java +++ b/ibm-mq-metrics/src/integrationTest/java/io/opentelemetry/ibm/mq/integration/tests/WMQMonitorIntegrationTest.java @@ -18,6 +18,8 @@ import com.ibm.mq.headers.pcf.PCFException; import com.ibm.mq.headers.pcf.PCFMessage; import com.ibm.mq.headers.pcf.PCFMessageAgent; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.ibm.mq.config.QueueManager; import io.opentelemetry.ibm.mq.opentelemetry.ConfigWrapper; @@ -281,4 +283,33 @@ void test_otlphttp() throws Exception { // reads a value from the heartbeat gauge assertThat(metricNames).contains("ibm.mq.heartbeat"); } + + @Test + void test_bad_connection() throws Exception { + logger.info("\n\n\n\n\n\nRunning test: test_bad_connection"); + String configFile = getConfigFile("conf/test-bad-config.yml"); + + ConfigWrapper config = ConfigWrapper.parse(configFile); + Meter meter = otelTesting.getOpenTelemetry().getMeter("opentelemetry.io/mq"); + TestWMQMonitor monitor = new TestWMQMonitor(config, meter, service); + monitor.runTest(); + + List data = otelTesting.getMetrics(); + + assertThat(data).isNotEmpty(); + assertThat(data).hasSize(2); + + Attributes attrs = null; + for (MetricData metricData : data) { + if ("ibm.mq.connection.errors".equals(metricData.getName())) { + attrs = metricData.getData().getPoints().stream().iterator().next().getAttributes(); + } + } + + assertThat(attrs).isNotNull(); + + String value = attrs.get(AttributeKey.stringKey("error.code")); + + assertThat(value).isEqualTo("2538"); + } } diff --git a/ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml b/ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml new file mode 100644 index 000000000..e839e54ed --- /dev/null +++ b/ibm-mq-metrics/src/integrationTest/resources/conf/test-bad-config.yml @@ -0,0 +1,302 @@ +queueManagers: + - name: "QM1" + host: "localhost" + port: 1417 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Client" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + channelName: DEV.ADMIN.SVRCONN + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + username: "admin" + password: "passw0rd" + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","$SYS"] + +mqMetrics: + # This Object will extract queue manager metrics + - metricsType: "queueMgrMetrics" + metrics: + include: + - Status: + alias: "Status" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_Q_MGR_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "AVERAGE" + clusterRollUpType: "INDIVIDUAL" + - ConnectionCount: + alias: "ConnectionCount" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_CONNECTION_COUNT" + + - ReusableLogSize: + alias: "Reusable Log Size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_REUSABLE_LOG_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_MGR_STATUS" + + - RestartLogSize: + alias: "Restart Log Size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_RESTART_LOG_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_MGR_STATUS" + + - ArchiveLogSize: + alias: "Archive Log Size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_ARCHIVE_LOG_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_MGR_STATUS" + + - StatisticsInterval: + alias: "Statistics Interval" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_STATISTICS_INTERVAL" + ibmCommand: "MQCMD_INQUIRE_Q_MGR" + + # This Object will extract queue metrics + - metricsType: "queueMetrics" + metrics: + include: + - MaxQueueDepth: + alias: "Max Queue Depth" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MAX_Q_DEPTH" + ibmCommand: "MQCMD_INQUIRE_Q" + + - CurrentQueueDepth: + alias: "Current Queue Depth" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_CURRENT_Q_DEPTH" + ibmCommand: "MQCMD_INQUIRE_Q" + + - CurrentQueueFileSize: + alias: "Current queue file size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_CUR_Q_FILE_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - MaxQueueFileSize: + alias: "Current maximum queue file size" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_CUR_MAX_FILE_SIZE" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - OpenInputCount: + alias: "Open Input Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_OPEN_INPUT_COUNT" + ibmCommand: "MQCMD_INQUIRE_Q" + + - OpenOutputCount: + alias: "Open Output Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_OPEN_OUTPUT_COUNT" + ibmCommand: "MQCMD_INQUIRE_Q" + + - OldestMsgAge: + alias: "OldestMsgAge" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_OLDEST_MSG_AGE" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "CURRENT" + clusterRollUpType: "INDIVIDUAL" + + - UncommittedMsgs: + alias: "UncommittedMsgs" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_UNCOMMITTED_MSGS" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - OnQTime: + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_Q_TIME_INDICATOR" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "CURRENT" + clusterRollUpType: "INDIVIDUAL" + + - HighQDepth: + alias: "HighQDepth" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_HIGH_Q_DEPTH" + ibmCommand: "MQCMD_RESET_Q_STATS" + + - MsgDeqCount: + alias: "MsgDeqCount" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MSG_DEQ_COUNT" + ibmCommand: "MQCMD_RESET_Q_STATS" + + - MsgEnqCount: + alias: "MsgEnqCount" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MSG_ENQ_COUNT" + ibmCommand: "MQCMD_RESET_Q_STATS" + + - UncommittedMsgs: + alias: "Uncommitted Messages" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACF_UNCOMMITTED_MSGS" + ibmCommand: "MQCMD_INQUIRE_Q_STATUS" + + - ServiceInterval: + alias: "Service Interval" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_Q_SERVICE_INTERVAL" + ibmCommand: "MQCMD_INQUIRE_Q" + + - ServiceIntervalEvent: + alias: "Service Interval Event" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_Q_SERVICE_INTERVAL_EVENT" + ibmCommand: "MQCMD_INQUIRE_Q" + + # This Object will extract channel metrics + - metricsType: "channelMetrics" + metrics: + include: + - Messages: + alias: "Messages" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MSGS" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - Status: + alias: "Status" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_CHANNEL_STATUS" #http://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.dev.doc/q090880_.htm + aggregationType: "OBSERVATION" + timeRollUpType: "AVERAGE" + clusterRollUpType: "INDIVIDUAL" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - ByteSent: + alias: "Byte Sent" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BYTES_SENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - ByteReceived: + alias: "Byte Received" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BYTES_RECEIVED" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - BuffersSent: + alias: "Buffers Sent" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BUFFERS_SENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - BuffersReceived: + alias: "Buffers Received" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_BUFFERS_RECEIVED" + ibmCommand: "MQCMD_INQUIRE_CHANNEL_STATUS" + + - CurrentSharingConversations: + alias: "Current Sharing Conversations" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_CURRENT_SHARING_CONVS" + + - MaxSharingConversations: + alias: "Max Sharing Conversations" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MAX_SHARING_CONVS" + + - MaxInstances: + alias: "Max Instances" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MAX_INSTANCES" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MaxInstancesPerClient: + alias: "Max Instances per Client" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MAX_INSTS_PER_CLIENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MsgRetryCount: + alias: "Message Retry Count" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MR_COUNT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MsgsReceived: + alias: "Message Received Count" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MSGS_RECEIVED" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - MsgsSent: + alias: "Message Sent" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_MSGS_SENT" + ibmCommand: "MQCMD_INQUIRE_CHANNEL" + + - metricsType: "listenerMetrics" + metrics: + include: + - Status: + alias: "Status" + ibmConstant: "com.ibm.mq.constants.CMQCFC.MQIACH_LISTENER_STATUS" + aggregationType: "OBSERVATION" + timeRollUpType: "AVERAGE" + clusterRollUpType: "INDIVIDUAL" + + # This Object will extract topic metrics + - metricsType: "topicMetrics" + metrics: + include: + - PublishCount: + alias: "Publish Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_PUB_COUNT" + ibmCommand: "MQCMD_INQUIRE_TOPIC_STATUS" + - SubscriptionCount: + alias: "Subscription Count" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_SUB_COUNT" + ibmCommand: "MQCMD_INQUIRE_TOPIC_STATUS" + + # Sets up reading configuration events from the configuration queue. + - metricsType: "configurationMetrics" + metrics: + include: + - MaxHandles: + alias: "Max Handles" + ibmConstant: "com.ibm.mq.constants.CMQC.MQIA_MAX_HANDLES" + +#Run it as a scheduled task instead of running every minute. +#If you want to run this every minute, comment this out +#taskSchedule: +# numberOfThreads: 1 +# taskDelaySeconds: 300 + + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + trustStoreEncryptedPassword: "" + + keyStorePath: "" + keyStorePassword: "" + keyStoreEncryptedPassword: "" + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: http://0.0.0.0:4318 diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java index c0223df86..832527fb3 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java @@ -5,12 +5,15 @@ package io.opentelemetry.ibm.mq; +import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.ERROR_CODE; import static io.opentelemetry.ibm.mq.metrics.IbmMqAttributes.IBM_MQ_QUEUE_MANAGER; import com.fasterxml.jackson.databind.ObjectMapper; +import com.ibm.mq.MQException; import com.ibm.mq.MQQueueManager; import com.ibm.mq.headers.pcf.PCFMessageAgent; import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.metrics.LongGauge; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.ibm.mq.config.QueueManager; @@ -46,6 +49,7 @@ public class WmqMonitor { private final List queueManagers; private final List> jobs = new ArrayList<>(); + private final LongCounter errorCodesCounter; private final LongGauge heartbeatGauge; private final ExecutorService threadPool; private final MetricsConfig metricsConfig; @@ -68,6 +72,7 @@ public WmqMonitor(ConfigWrapper config, ExecutorService threadPool, Meter meter) this.metricsConfig = new MetricsConfig(config); this.heartbeatGauge = Metrics.createIbmMqHeartbeat(meter); + this.errorCodesCounter = Metrics.createIbmMqConnectionErrors(meter); this.threadPool = threadPool; jobs.add(new QueueManagerMetricsCollector(meter)); @@ -107,6 +112,12 @@ public void run(QueueManager queueManager) { Thread.currentThread().getName(), e.getMessage(), e); + if (e.getCause() instanceof MQException) { + MQException mqe = (MQException) e.getCause(); + String errorCode = String.valueOf(mqe.getReason()); + errorCodesCounter.add( + 1, Attributes.of(IBM_MQ_QUEUE_MANAGER, queueManagerName, ERROR_CODE, errorCode)); + } } finally { if (this.metricsConfig.isIbmMqHeartbeatEnabled()) { heartbeatGauge.set( diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java index 6caf87c48..fd84b6871 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/IbmMqAttributes.java @@ -47,5 +47,8 @@ public final class IbmMqAttributes { /** Logical name of the service. */ public static final AttributeKey SERVICE_NAME = stringKey("service.name"); + /** The reason code associated with an error */ + public static final AttributeKey ERROR_CODE = stringKey("error.code"); + private IbmMqAttributes() {} } diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java index c54c9a431..3698c87c9 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/Metrics.java @@ -421,4 +421,12 @@ public static LongGauge createIbmMqManagerMaxHandles(Meter meter) { .setDescription("Max open handles") .build(); } + + public static LongCounter createIbmMqConnectionErrors(Meter meter) { + return meter + .counterBuilder("ibm.mq.connection.errors") + .setUnit("{errors}") + .setDescription("Number of connection errors") + .build(); + } } diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java index c172cd532..acc975f8f 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metrics/MetricsConfig.java @@ -199,6 +199,10 @@ public boolean isIbmMqManagerMaxHandlesEnabled() { return isEnabled("ibm.mq.manager.max.handles"); } + public boolean isIbmMqConnectionErrorsEnabled() { + return isEnabled("ibm.mq.connection.errors"); + } + private boolean isEnabled(String key) { Object metricInfo = config.get(key); if (!(metricInfo instanceof Map)) { From 2765b97e89ecaede9e9f0610b57eddcad31434c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:48:25 +0200 Subject: [PATCH 197/371] Opamp first iteration completion (#2067) --- buildSrc/build.gradle.kts | 1 + .../otel.animalsniffer-conventions.gradle.kts | 19 ++++++ disk-buffering/build.gradle.kts | 18 +----- opamp-client/README.md | 50 ++++++++++++++++ opamp-client/build.gradle.kts | 3 + .../opamp/client/internal/OpampClient.java | 44 ++++---------- .../client/internal/OpampClientBuilder.java | 8 +-- .../connectivity/http/OkHttpSender.java | 14 ++++- .../client/internal/impl/OpampClientImpl.java | 59 ++++++++----------- .../ExponentialBackoffPeriodicDelay.java | 38 ++++++++++++ .../request/delay/RetryPeriodicDelay.java | 41 +++++++++++++ .../request/service/HttpRequestService.java | 5 +- .../service/WebSocketRequestService.java | 3 +- .../internal/impl/OpampClientImplTest.java | 21 ++----- .../ExponentialBackoffPeriodicDelayTest.java | 29 +++++++++ .../request/delay/RetryPeriodicDelayTest.java | 31 ++++++++++ 16 files changed, 269 insertions(+), 115 deletions(-) create mode 100644 buildSrc/src/main/kotlin/otel.animalsniffer-conventions.gradle.kts create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelay.java create mode 100644 opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelay.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelayTest.java create mode 100644 opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelayTest.java diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index ba5b81ceb..bf473fff1 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,6 +16,7 @@ dependencies { implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.3") + implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") } spotless { diff --git a/buildSrc/src/main/kotlin/otel.animalsniffer-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.animalsniffer-conventions.gradle.kts new file mode 100644 index 000000000..3fda84f4c --- /dev/null +++ b/buildSrc/src/main/kotlin/otel.animalsniffer-conventions.gradle.kts @@ -0,0 +1,19 @@ +import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer + +plugins { + id("otel.java-conventions") + id("ru.vyarus.animalsniffer") +} + +dependencies { + signature("com.toasttab.android:gummy-bears-api-21:0.12.0:coreLib@signature") +} + +animalsniffer { + sourceSets = listOf(java.sourceSets.main.get()) +} + +// Always having declared output makes this task properly participate in tasks up-to-date checks +tasks.withType { + reports.text.required.set(true) +} diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index b124c50ba..d2afee7e2 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -1,12 +1,11 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar -import ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer plugins { id("otel.java-conventions") id("otel.publish-conventions") + id("otel.animalsniffer-conventions") id("com.gradleup.shadow") id("me.champeau.jmh") version "0.7.3" - id("ru.vyarus.animalsniffer") version "2.0.1" id("com.squareup.wire") version "5.3.11" } @@ -20,27 +19,12 @@ dependencies { implementation("io.opentelemetry:opentelemetry-api-incubator") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") - signature("com.toasttab.android:gummy-bears-api-21:0.12.0:coreLib@signature") testImplementation("org.mockito:mockito-inline") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") protos("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha@jar") } -animalsniffer { - sourceSets = listOf(java.sourceSets.main.get()) -} - -// Always having declared output makes this task properly participate in tasks up-to-date checks -tasks.withType { - reports.text.required.set(true) -} - -// Attaching animalsniffer check to the compilation process. -tasks.named("classes").configure { - finalizedBy("animalsnifferMain") -} - jmh { warmupIterations.set(0) fork.set(2) diff --git a/opamp-client/README.md b/opamp-client/README.md index 9cb2dd97d..d8c0369a1 100644 --- a/opamp-client/README.md +++ b/opamp-client/README.md @@ -3,6 +3,56 @@ Java implementation of the OpAMP client [spec](https://github.com/open-telemetry/opamp-spec/blob/main/specification.md). +> [!WARNING] +> This is an incubating feature. Breaking changes can happen on a new release without previous +> notice and without backward compatibility guarantees. + +## Usage + +```java +// Initializing it + +RequestService requestService = HttpRequestService.create(OkHttpSender.create("[OPAMP_SERVICE_URL]")); +// RequestService requestService = WebSocketRequestService.create(OkHttpWebSocket.create("[OPAMP_SERVICE_URL]")); // Use this instead to connect to the server via WebSocket. +OpampClient client = + OpampClient.builder() + .putIdentifyingAttribute("service.name", "My service name") + .enableRemoteConfig() + .setRequestService(requestService) + .build( + new OpampClient.Callbacks() { + @Override + public void onConnect() {} + + @Override + public void onConnectFailed(@Nullable Throwable throwable) {} + + @Override + public void onErrorResponse(ServerErrorResponse errorResponse) {} + + @Override + public void onMessage(MessageData messageData) { + AgentRemoteConfig remoteConfig = messageData.getRemoteConfig(); + if (remoteConfig != null) { + // A remote config was received + + // After applying it... + client.setRemoteConfigStatus( + new RemoteConfigStatus.Builder() + .status(RemoteConfigStatuses.RemoteConfigStatuses_APPLIED) + .build()); + } + } + }); + +// State update +client.setAgentDescription(new AgentDescription.Builder().build()); + +// App shutdown +client.close(); + +``` + ## Component owners - [Cesar Munoz](https://github.com/LikeTheSalad), Elastic diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index c4400d944..1aeed0c40 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -4,6 +4,8 @@ import java.net.URL plugins { id("otel.java-conventions") + id("otel.publish-conventions") + id("otel.animalsniffer-conventions") id("de.undercouch.download") version "5.6.0" id("com.squareup.wire") version "5.3.11" } @@ -14,6 +16,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.opamp.client") dependencies { implementation("com.squareup.okhttp3:okhttp") implementation("com.github.f4b6a3:uuid-creator") + implementation("io.opentelemetry:opentelemetry-api") annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") testImplementation("org.mockito:mockito-inline") diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java index c835c6f55..5889642d8 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java @@ -6,45 +6,23 @@ package io.opentelemetry.opamp.client.internal; import io.opentelemetry.opamp.client.internal.response.MessageData; +import java.io.Closeable; import javax.annotation.Nullable; import opamp.proto.AgentDescription; import opamp.proto.RemoteConfigStatus; import opamp.proto.ServerErrorResponse; -public interface OpampClient { +public interface OpampClient extends Closeable { static OpampClientBuilder builder() { return new OpampClientBuilder(); } /** - * Starts the client and begin attempts to connect to the Server. Once connection is established - * the client will attempt to maintain it by reconnecting if the connection is lost. All failed - * connection attempts will be reported via {@link Callbacks#onConnectFailed(Throwable)} callback. - * - *

This method does not wait until the connection to the Server is established and will likely - * return before the connection attempts are even made. - * - *

This method may be called only once. - * - * @param callbacks The Callback to which the Client will notify about any Server requests and - * responses. - */ - void start(Callbacks callbacks); - - /** - * Stops the client. May be called only after {@link #start(Callbacks)}. May be called only once. - * After this call returns successfully it is guaranteed that no callbacks will be called. Once - * stopped, the client cannot be started again. - */ - void stop(); - - /** - * Sets attributes of the Agent. The attributes will be included in the next status report sent to - * the Server. When called after {@link #start(Callbacks)}, the attributes will be included in the - * next outgoing status report. This is typically used by Agents which allow their - * AgentDescription to change dynamically while the OpAMPClient is started. May be also called - * from {@link Callbacks#onMessage(MessageData)}. + * Sets attributes of the Agent. The attributes will be included in the next outgoing status + * report. This is typically used by Agents which allow their AgentDescription to change + * dynamically while the OpAMPClient is started. May be also called from {@link + * Callbacks#onMessage(MessageData)}. * * @param agentDescription The new agent description. */ @@ -59,16 +37,14 @@ static OpampClientBuilder builder() { interface Callbacks { /** - * Called when the connection is successfully established to the Server. May be called after - * {@link #start(Callbacks)} is called and every time a connection is established to the Server. - * For WebSocket clients this is called after the handshake is completed without any error. For - * HTTP clients this is called for any request if the response status is OK. + * Called when the connection is successfully established to the Server. For WebSocket clients + * this is called after the handshake is completed without any error. For HTTP clients this is + * called for any request if the response status is OK. */ void onConnect(); /** - * Called when the connection to the Server cannot be established. May be called after {@link - * #start(Callbacks)} is called and tries to connect to the Server. May also be called if the + * Called when the connection to the Server cannot be established. May also be called if the * connection is lost and reconnection attempt fails. * * @param throwable The exception. diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java index 7e021c716..3e4d0f4aa 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java @@ -381,11 +381,7 @@ public OpampClientBuilder setEffectiveConfigState(State.EffectiveConfig effectiv return this; } - public OpampClient build() { - if (service == null) { - throw new IllegalStateException( - "The request service is not set. You must provide it by calling setRequestService()"); - } + public OpampClient build(OpampClient.Callbacks callbacks) { List protoIdentifyingAttributes = new ArrayList<>(); List protoNonIdentifyingAttributes = new ArrayList<>(); identifyingAttributes.forEach( @@ -411,7 +407,7 @@ public OpampClient build() { new State.InstanceUid(instanceUid), new State.Flags(0L), effectiveConfigState); - return OpampClientImpl.create(service, state); + return OpampClientImpl.create(service, state, callbacks); } private static State.EffectiveConfig createEffectiveConfigNoop() { diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java index fcfc97ee8..1add016fd 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/http/OkHttpSender.java @@ -5,6 +5,7 @@ package io.opentelemetry.opamp.client.internal.connectivity.http; +import io.opentelemetry.api.internal.InstrumentationUtil; import java.io.IOException; import java.io.InputStream; import java.util.concurrent.CompletableFuture; @@ -13,6 +14,7 @@ import okhttp3.Callback; import okhttp3.MediaType; import okhttp3.OkHttpClient; +import okhttp3.Request; import okhttp3.RequestBody; import okio.BufferedSink; @@ -45,8 +47,16 @@ public CompletableFuture send(BodyWriter writer, int contentLength) { RequestBody body = new RawRequestBody(writer, contentLength, MEDIA_TYPE); builder.post(body); + // By suppressing instrumentations, we prevent automatic instrumentations for the okhttp request + // that polls the opamp server. + InstrumentationUtil.suppressInstrumentation(() -> doSendRequest(builder.build(), future)); + + return future; + } + + private void doSendRequest(Request request, CompletableFuture future) { client - .newCall(builder.build()) + .newCall(request) .enqueue( new Callback() { @Override @@ -59,8 +69,6 @@ public void onFailure(Call call, IOException e) { future.completeExceptionally(e); } }); - - return future; } private static class OkHttpResponse implements Response { diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java index 8dcf6bb7e..7ada06c42 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java @@ -28,11 +28,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import okio.ByteString; import opamp.proto.AgentDescription; import opamp.proto.AgentToServer; @@ -51,9 +49,8 @@ public final class OpampClientImpl private final AgentToServerAppenders appenders; private final OpampClientState state; private final RecipeManager recipeManager; - private final AtomicBoolean isRunning = new AtomicBoolean(false); private final AtomicBoolean hasStopped = new AtomicBoolean(false); - @Nullable private Callbacks callbacks; + private final Callbacks callbacks; /** Fields that must always be sent. */ private static final List REQUIRED_FIELDS; @@ -84,7 +81,8 @@ public final class OpampClientImpl COMPRESSABLE_FIELDS = Collections.unmodifiableList(compressableFields); } - public static OpampClientImpl create(RequestService requestService, OpampClientState state) { + public static OpampClientImpl create( + RequestService requestService, OpampClientState state, Callbacks callbacks) { AgentToServerAppenders appenders = new AgentToServerAppenders( AgentDescriptionAppender.create(state.agentDescription), @@ -95,41 +93,35 @@ public static OpampClientImpl create(RequestService requestService, OpampClientS InstanceUidAppender.create(state.instanceUid), FlagsAppender.create(state.flags), AgentDisconnectAppender.create()); - return new OpampClientImpl( - requestService, appenders, state, RecipeManager.create(REQUIRED_FIELDS)); + OpampClientImpl client = + new OpampClientImpl( + requestService, appenders, state, RecipeManager.create(REQUIRED_FIELDS), callbacks); + + // Start + requestService.start(client, client); + client.disableCompression(); + client.startObservingStateChange(); + requestService.sendRequest(); + + return client; } private OpampClientImpl( RequestService requestService, AgentToServerAppenders appenders, OpampClientState state, - RecipeManager recipeManager) { + RecipeManager recipeManager, + Callbacks callbacks) { this.requestService = requestService; this.appenders = appenders; this.state = state; this.recipeManager = recipeManager; + this.callbacks = callbacks; } @Override - public void start(@Nonnull Callbacks callbacks) { - if (hasStopped.get()) { - throw new IllegalStateException("The client cannot start after it has been stopped."); - } - if (isRunning.compareAndSet(false, true)) { - this.callbacks = callbacks; - requestService.start(this, this); - disableCompression(); - startObservingStateChange(); - requestService.sendRequest(); - } else { - throw new IllegalStateException("The client has already been started"); - } - } - - @Override - public void stop() { - if (isRunning.compareAndSet(true, false)) { - hasStopped.set(true); + public void close() { + if (hasStopped.compareAndSet(false, true)) { stopObservingStateChange(); prepareDisconnectRequest(); requestService.stop(); @@ -154,12 +146,12 @@ public void setRemoteConfigStatus(@Nonnull RemoteConfigStatus remoteConfigStatus @Override public void onConnectionSuccess() { - getCallbacks().onConnect(); + callbacks.onConnect(); } @Override public void onConnectionFailed(Throwable throwable) { - getCallbacks().onConnectFailed(throwable); + callbacks.onConnectFailed(throwable); } @Override @@ -176,7 +168,7 @@ public void onRequestFailed(Throwable throwable) { preserveFailedRequestRecipe(); if (throwable instanceof OpampServerResponseException) { ServerErrorResponse errorResponse = ((OpampServerResponseException) throwable).errorResponse; - getCallbacks().onErrorResponse(errorResponse); + callbacks.onErrorResponse(errorResponse); } } @@ -203,7 +195,7 @@ private void handleResponsePayload(ServerToAgent response) { } if (notifyOnMessage) { - getCallbacks().onMessage(messageBuilder.build()); + callbacks.onMessage(messageBuilder.build()); } } @@ -224,11 +216,6 @@ private void prepareDisconnectRequest() { recipeManager.next().addField(Field.AGENT_DISCONNECT); } - @Nonnull - private Callbacks getCallbacks() { - return Objects.requireNonNull(callbacks); - } - @Override public Request get() { AgentToServer.Builder builder = new AgentToServer.Builder(); diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelay.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelay.java new file mode 100644 index 000000000..88b608c7e --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelay.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import java.time.Duration; +import javax.annotation.concurrent.GuardedBy; + +public final class ExponentialBackoffPeriodicDelay implements PeriodicDelay { + private final Duration initialDelay; + private final Object delayNanosLock = new Object(); + + @GuardedBy("delayNanosLock") + private long delayNanos; + + public ExponentialBackoffPeriodicDelay(Duration initialDelay) { + this.initialDelay = initialDelay; + delayNanos = initialDelay.toNanos(); + } + + @Override + public Duration getNextDelay() { + synchronized (delayNanosLock) { + long previousValue = delayNanos; + delayNanos = delayNanos * 2; + return Duration.ofNanos(previousValue); + } + } + + @Override + public void reset() { + synchronized (delayNanosLock) { + delayNanos = initialDelay.toNanos(); + } + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelay.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelay.java new file mode 100644 index 000000000..46f1a21da --- /dev/null +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelay.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import java.time.Duration; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +/** Defaults to an exponential backoff strategy, unless a delay is suggested. */ +public final class RetryPeriodicDelay implements PeriodicDelay, AcceptsDelaySuggestion { + private final ExponentialBackoffPeriodicDelay exponentialBackoff; + private final AtomicReference currentDelay; + + public static RetryPeriodicDelay create(Duration initialDelay) { + return new RetryPeriodicDelay(new ExponentialBackoffPeriodicDelay(initialDelay)); + } + + private RetryPeriodicDelay(ExponentialBackoffPeriodicDelay exponentialBackoff) { + this.exponentialBackoff = exponentialBackoff; + currentDelay = new AtomicReference<>(exponentialBackoff); + } + + @Override + public void suggestDelay(Duration delay) { + currentDelay.set(PeriodicDelay.ofFixedDuration(delay)); + } + + @Override + public Duration getNextDelay() { + return Objects.requireNonNull(currentDelay.get()).getNextDelay(); + } + + @Override + public void reset() { + exponentialBackoff.reset(); + currentDelay.set(exponentialBackoff); + } +} diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java index da1759c22..569447342 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java @@ -11,6 +11,7 @@ import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; +import io.opentelemetry.opamp.client.internal.request.delay.RetryPeriodicDelay; import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; import java.io.IOException; @@ -47,6 +48,8 @@ public final class HttpRequestService implements RequestService { @Nullable private Supplier requestSupplier; public static final PeriodicDelay DEFAULT_DELAY_BETWEEN_REQUESTS = PeriodicDelay.ofFixedDuration(Duration.ofSeconds(30)); + public static final PeriodicDelay DEFAULT_DELAY_BETWEEN_RETRIES = + RetryPeriodicDelay.create(Duration.ofSeconds(30)); /** * Creates an {@link HttpRequestService}. @@ -54,7 +57,7 @@ public final class HttpRequestService implements RequestService { * @param requestSender The HTTP sender implementation. */ public static HttpRequestService create(HttpSender requestSender) { - return create(requestSender, DEFAULT_DELAY_BETWEEN_REQUESTS, DEFAULT_DELAY_BETWEEN_REQUESTS); + return create(requestSender, DEFAULT_DELAY_BETWEEN_REQUESTS, DEFAULT_DELAY_BETWEEN_RETRIES); } /** diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java index 2bd283631..13ef9b117 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java @@ -10,6 +10,7 @@ import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.request.delay.AcceptsDelaySuggestion; import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; +import io.opentelemetry.opamp.client.internal.request.delay.RetryPeriodicDelay; import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; import java.io.ByteArrayOutputStream; @@ -30,7 +31,7 @@ public final class WebSocketRequestService implements RequestService, WebSocket.Listener { private static final PeriodicDelay DEFAULT_DELAY_BETWEEN_RETRIES = - PeriodicDelay.ofFixedDuration(Duration.ofSeconds(30)); + RetryPeriodicDelay.create(Duration.ofSeconds(30)); private final WebSocket webSocket; private final AtomicBoolean isRunning = new AtomicBoolean(false); diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java index 764977730..9a076f757 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.opamp.client.internal.impl; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.never; @@ -90,7 +89,7 @@ void setUp() { @AfterEach void tearDown() { - client.stop(); + client.close(); } @Test @@ -178,23 +177,12 @@ void verifyStop() { initializeClient(); enqueueServerToAgentResponse(new ServerToAgent.Builder().build()); - client.stop(); + client.close(); AgentToServer agentToServerMessage = getAgentToServerMessage(takeRequest()); assertThat(agentToServerMessage.agent_disconnect).isNotNull(); } - @Test - void verifyStartOnlyOnce() { - initializeClient(); - try { - client.start(callbacks); - fail("Should have thrown an exception"); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("The client has already been started"); - } - } - @Test void onSuccess_withChangesToReport_notifyCallbackOnMessage() { initializeClient(); @@ -403,13 +391,12 @@ private RecordedRequest initializeClient() { } private RecordedRequest initializeClient(ServerToAgent initialResponse) { - client = OpampClientImpl.create(requestService, state); - // Prepare first request on start enqueueServerToAgentResponse(initialResponse); callbacks = spy(new TestCallbacks()); - client.start(callbacks); + client = OpampClientImpl.create(requestService, state, callbacks); + return takeRequest(); } diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelayTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelayTest.java new file mode 100644 index 000000000..ff7a1e186 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/ExponentialBackoffPeriodicDelayTest.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class ExponentialBackoffPeriodicDelayTest { + @Test + void verifyDelayUpdates() { + ExponentialBackoffPeriodicDelay delay = + new ExponentialBackoffPeriodicDelay(Duration.ofSeconds(1)); + + assertThat(delay.getNextDelay()).isEqualTo(Duration.ofSeconds(1)); + assertThat(delay.getNextDelay()).isEqualTo(Duration.ofSeconds(2)); + assertThat(delay.getNextDelay()).isEqualTo(Duration.ofSeconds(4)); + assertThat(delay.getNextDelay()).isEqualTo(Duration.ofSeconds(8)); + assertThat(delay.getNextDelay()).isEqualTo(Duration.ofSeconds(16)); + + // Reset + delay.reset(); + assertThat(delay.getNextDelay()).isEqualTo(Duration.ofSeconds(1)); + } +} diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelayTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelayTest.java new file mode 100644 index 000000000..88bda4ec7 --- /dev/null +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/delay/RetryPeriodicDelayTest.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.opamp.client.internal.request.delay; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.Duration; +import org.junit.jupiter.api.Test; + +class RetryPeriodicDelayTest { + @Test + public void verifyDelayBehavior() { + RetryPeriodicDelay retryPeriodicDelay = RetryPeriodicDelay.create(Duration.ofSeconds(1)); + + // Without suggested delay + assertThat(retryPeriodicDelay.getNextDelay()).isEqualTo(Duration.ofSeconds(1)); + assertThat(retryPeriodicDelay.getNextDelay()).isEqualTo(Duration.ofSeconds(2)); + assertThat(retryPeriodicDelay.getNextDelay()).isEqualTo(Duration.ofSeconds(4)); + retryPeriodicDelay.reset(); + assertThat(retryPeriodicDelay.getNextDelay()).isEqualTo(Duration.ofSeconds(1)); + + // With suggested delay + retryPeriodicDelay.suggestDelay(Duration.ofSeconds(5)); + assertThat(retryPeriodicDelay.getNextDelay()).isEqualTo(Duration.ofSeconds(5)); + retryPeriodicDelay.reset(); + assertThat(retryPeriodicDelay.getNextDelay()).isEqualTo(Duration.ofSeconds(1)); + } +} From d6c564ea1b98c6d880cb6d94aeba6809ca86067d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Tue, 19 Aug 2025 23:56:28 +0200 Subject: [PATCH 198/371] Disk buffering api changes (#2084) --- .../contrib/disk/buffering/SignalType.java | 12 ++ .../buffering/exporters/ExporterCallback.java | 38 +++++ .../exporters/NoopExporterCallback.java | 24 ++++ .../exporters/SignalStorageExporter.java | 54 +++++++ .../exporters/SpanToDiskExporter.java | 76 ++++++++++ .../buffering/exporters/package-info.java | 9 ++ .../internal/storage/FileSpanStorage.java | 39 +++++ .../disk/buffering/storage/SignalStorage.java | 55 +++++++ .../storage/result/DefaultWriteResult.java | 29 ++++ .../buffering/storage/result/WriteResult.java | 31 ++++ .../exporters/SignalStorageExporterTest.java | 135 ++++++++++++++++++ 11 files changed, 502 insertions(+) create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/package-info.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/DefaultWriteResult.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java create mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java new file mode 100644 index 000000000..c66ed940e --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java @@ -0,0 +1,12 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering; + +public enum SignalType { + SPAN, + LOG, + METRIC +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java new file mode 100644 index 000000000..f877c7b7d --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java @@ -0,0 +1,38 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import io.opentelemetry.contrib.disk.buffering.SignalType; +import javax.annotation.Nullable; + +/** Notifies about exporter and storage-related operations from within a signal to disk exporter. */ +public interface ExporterCallback { + /** + * Called when an export to disk operation succeeded. + * + * @param type The type of signal associated to the exporter. + */ + void onExportSuccess(SignalType type); + + /** + * Called when an export to disk operation failed. + * + * @param type The type of signal associated to the exporter. + * @param error Optional - provides more information of why the operation failed. + */ + void onExportError(SignalType type, @Nullable Throwable error); + + /** + * Called when the exporter is closed. + * + * @param type The type of signal associated to the exporter. + */ + void onShutdown(SignalType type); + + static ExporterCallback noop() { + return NoopExporterCallback.INSTANCE; + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java new file mode 100644 index 000000000..2dd4f2f70 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java @@ -0,0 +1,24 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import io.opentelemetry.contrib.disk.buffering.SignalType; +import javax.annotation.Nullable; + +final class NoopExporterCallback implements ExporterCallback { + static final NoopExporterCallback INSTANCE = new NoopExporterCallback(); + + private NoopExporterCallback() {} + + @Override + public void onExportSuccess(SignalType type) {} + + @Override + public void onExportError(SignalType type, @Nullable Throwable error) {} + + @Override + public void onShutdown(SignalType type) {} +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java new file mode 100644 index 000000000..51d135299 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java @@ -0,0 +1,54 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import io.opentelemetry.contrib.disk.buffering.SignalType; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.common.CompletableResultCode; +import java.time.Duration; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** Internal utility for common export to disk operations across all exporters. */ +final class SignalStorageExporter { + private final SignalStorage storage; + private final ExporterCallback callback; + private final Duration writeTimeout; + private final SignalType type; + + public SignalStorageExporter( + SignalStorage storage, ExporterCallback callback, Duration writeTimeout, SignalType type) { + this.storage = storage; + this.callback = callback; + this.writeTimeout = writeTimeout; + this.type = type; + } + + public CompletableResultCode exportToStorage(Collection items) { + CompletableFuture future = storage.write(items); + try { + WriteResult operation = future.get(writeTimeout.toMillis(), TimeUnit.MILLISECONDS); + if (operation.isSuccessful()) { + callback.onExportSuccess(type); + return CompletableResultCode.ofSuccess(); + } + + Throwable error = operation.getError(); + callback.onExportError(type, error); + if (error != null) { + return CompletableResultCode.ofExceptionalFailure(error); + } + return CompletableResultCode.ofFailure(); + } catch (ExecutionException | InterruptedException | TimeoutException e) { + callback.onExportError(type, e); + return CompletableResultCode.ofExceptionalFailure(e); + } + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java new file mode 100644 index 000000000..2bda19da9 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java @@ -0,0 +1,76 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.contrib.disk.buffering.SignalType; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.export.SpanExporter; +import java.time.Duration; +import java.util.Collection; + +/** Exporter that stores spans into disk. */ +public final class SpanToDiskExporter implements SpanExporter { + private final SignalStorageExporter storageExporter; + private final ExporterCallback callback; + private static final SignalType TYPE = SignalType.SPAN; + + private SpanToDiskExporter( + SignalStorageExporter storageExporter, ExporterCallback callback) { + this.storageExporter = storageExporter; + this.callback = callback; + } + + public Builder builder(SignalStorage.Span storage) { + return new Builder(storage); + } + + @Override + public CompletableResultCode export(Collection spans) { + return storageExporter.exportToStorage(spans); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + callback.onShutdown(TYPE); + return CompletableResultCode.ofSuccess(); + } + + public static final class Builder { + private final SignalStorage.Span storage; + private ExporterCallback callback = ExporterCallback.noop(); + private Duration writeTimeout = Duration.ofSeconds(10); + + @CanIgnoreReturnValue + public Builder setExporterCallback(ExporterCallback value) { + callback = value; + return this; + } + + @CanIgnoreReturnValue + public Builder setWriteTimeout(Duration value) { + writeTimeout = value; + return this; + } + + public SpanToDiskExporter build() { + SignalStorageExporter storageExporter = + new SignalStorageExporter<>(storage, callback, writeTimeout, TYPE); + return new SpanToDiskExporter(storageExporter, callback); + } + + private Builder(SignalStorage.Span storage) { + this.storage = storage; + } + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/package-info.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/package-info.java new file mode 100644 index 000000000..6a367dca9 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/package-info.java @@ -0,0 +1,9 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +@ParametersAreNonnullByDefault +package io.opentelemetry.contrib.disk.buffering.exporters; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java new file mode 100644 index 000000000..5ba51790f --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.storage; + +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +/** Default storage implementation where items are stored in multiple protobuf files. */ +public final class FileSpanStorage implements SignalStorage.Span { + + @Override + public CompletableFuture write(Collection items) { + throw new UnsupportedOperationException("For next PR"); + } + + @Override + public CompletableFuture clear() { + throw new UnsupportedOperationException("For next PR"); + } + + @Override + public void close() { + throw new UnsupportedOperationException("For next PR"); + } + + @Nonnull + @Override + public Iterator> iterator() { + throw new UnsupportedOperationException("For next PR"); + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java new file mode 100644 index 000000000..e8daf391a --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java @@ -0,0 +1,55 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.storage; + +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.Closeable; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +/** + * Allows writing and iterating over written signal items. + * + * @param The type of signal data supported. + */ +public interface SignalStorage extends Iterable>, Closeable { + + /** + * Stores signal items. + * + * @param items The items to be stored. + * @return A future with {@link WriteResult}. + */ + CompletableFuture write(Collection items); + + /** + * Removes all the previously stored items. + * + * @return A future with {@link WriteResult}. + */ + CompletableFuture clear(); + + /** + * Abstraction for Spans. Implementations should use this instead of {@link SignalStorage} + * directly. + */ + interface Span extends SignalStorage {} + + /** + * Abstraction for Logs. Implementations should use this instead of {@link SignalStorage} + * directly. + */ + interface LogRecord extends SignalStorage {} + + /** + * Abstraction for Metrics. Implementations should use this instead of {@link SignalStorage} + * directly. + */ + interface Metric extends SignalStorage {} +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/DefaultWriteResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/DefaultWriteResult.java new file mode 100644 index 000000000..ff693c74b --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/DefaultWriteResult.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.storage.result; + +import javax.annotation.Nullable; + +final class DefaultWriteResult implements WriteResult { + private final boolean successful; + @Nullable private final Throwable error; + + DefaultWriteResult(boolean successful, @Nullable Throwable error) { + this.successful = successful; + this.error = error; + } + + @Override + public boolean isSuccessful() { + return successful; + } + + @Nullable + @Override + public Throwable getError() { + return error; + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java new file mode 100644 index 000000000..8e4534a55 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java @@ -0,0 +1,31 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.storage.result; + +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import javax.annotation.Nullable; + +/** The result of a {@link SignalStorage} write operation. */ +public interface WriteResult { + /** + * Whether the operation succeeded or not. + * + * @return `true` if the items have been successfully stored, `false` otherwise. + */ + boolean isSuccessful(); + + /** + * Provides details of why the operation failed. + * + * @return The error (if any) for the failed operation. It must be null for successful operations. + */ + @Nullable + Throwable getError(); + + static WriteResult create(boolean successful, @Nullable Throwable error) { + return new DefaultWriteResult(successful, error); + } +} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java new file mode 100644 index 000000000..6f7db99ef --- /dev/null +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java @@ -0,0 +1,135 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.when; + +import io.opentelemetry.contrib.disk.buffering.SignalType; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class SignalStorageExporterTest { + @Mock private ExporterCallback callback; + + @Test + void verifyExportToStorage_success() { + SignalStorage.Span storage = new TestSpanStorage(); + SignalType signalType = SignalType.SPAN; + SignalStorageExporter storageExporter = + new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1), signalType); + SpanData item1 = mock(); + SpanData item2 = mock(); + SpanData item3 = mock(); + + CompletableResultCode resultCode = storageExporter.exportToStorage(Arrays.asList(item1, item2)); + + assertThat(resultCode.isSuccess()).isTrue(); + verify(callback).onExportSuccess(signalType); + verifyNoMoreInteractions(callback); + + // Adding more items + clearInvocations(callback); + resultCode = storageExporter.exportToStorage(Collections.singletonList(item3)); + + assertThat(resultCode.isSuccess()).isTrue(); + verify(callback).onExportSuccess(signalType); + verifyNoMoreInteractions(callback); + + // Checking items + List storedItems = new ArrayList<>(); + for (Collection collection : storage) { + storedItems.addAll(collection); + } + assertThat(storedItems).containsExactly(item1, item2, item3); + } + + @SuppressWarnings("ThrowableNotThrown") + @Test + void verifyExportToStorage_failure() { + SignalStorage.Span storage = mock(); + SignalType signalType = SignalType.SPAN; + SignalStorageExporter storageExporter = + new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1), signalType); + SpanData item1 = mock(); + + // Without exception + when(storage.write(anyCollection())) + .thenReturn(CompletableFuture.completedFuture(WriteResult.create(false, null))); + + CompletableResultCode resultCode = + storageExporter.exportToStorage(Collections.singletonList(item1)); + + assertThat(resultCode.isSuccess()).isFalse(); + assertThat(resultCode.getFailureThrowable()).isNull(); + verify(callback).onExportError(signalType, null); + verifyNoMoreInteractions(callback); + + // With exception + clearInvocations(callback); + Exception exception = new Exception(); + when(storage.write(anyCollection())) + .thenReturn(CompletableFuture.completedFuture(WriteResult.create(false, exception))); + + resultCode = storageExporter.exportToStorage(Collections.singletonList(item1)); + + assertThat(resultCode.isSuccess()).isFalse(); + assertThat(resultCode.getFailureThrowable()).isEqualTo(exception); + verify(callback).onExportError(signalType, exception); + verifyNoMoreInteractions(callback); + } + + private static class TestSpanStorage implements SignalStorage.Span { + private final List> storedItems = new ArrayList<>(); + + @Override + public CompletableFuture write(Collection items) { + storedItems.add(items); + return getSuccessfulFuture(); + } + + @Override + public CompletableFuture clear() { + storedItems.clear(); + return getSuccessfulFuture(); + } + + @Override + public void close() {} + + @Nonnull + @Override + public Iterator> iterator() { + return storedItems.iterator(); + } + + @Nonnull + private static CompletableFuture getSuccessfulFuture() { + return CompletableFuture.completedFuture(WriteResult.create(true, null)); + } + } +} From 301fed34d8edeb02dad27467169893cb832b8d77 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 19 Aug 2025 19:07:08 -0700 Subject: [PATCH 199/371] Restructure workflows (#2135) --- .github/workflows/build-common.yml | 128 ++++++++++++++ .github/workflows/build-daily.yml | 35 ++++ .github/workflows/build-pull-request.yml | 32 ++++ .github/workflows/build.yml | 207 +---------------------- 4 files changed, 202 insertions(+), 200 deletions(-) create mode 100644 .github/workflows/build-common.yml create mode 100644 .github/workflows/build-daily.yml create mode 100644 .github/workflows/build-pull-request.yml diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml new file mode 100644 index 000000000..b11fdaa6c --- /dev/null +++ b/.github/workflows/build-common.yml @@ -0,0 +1,128 @@ +name: Reusable - Common + +on: + workflow_call: + inputs: + cache-read-only: + type: boolean + required: false + no-build-cache: + type: boolean + required: false + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up JDK for running Gradle + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: 17 + + - name: Set up gradle + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + with: + cache-read-only: ${{ inputs.cache-read-only }} + + - name: Gradle build and test + run: ./gradlew build -x test ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + + test: + name: Test + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: + - macos-latest + - macos-13 + - ubuntu-latest + - windows-latest + test-java-version: + - 8 + - 11 + - 17 + - 21 + - 24 # renovate: datasource=java-version + # macos-latest drops support for java 8 temurin. Run java 8 on macos-13. Run java 11, 17, 21 on macos-latest. + exclude: + - os: macos-latest + test-java-version: 8 + - os: macos-13 + test-java-version: 11 + - os: macos-13 + test-java-version: 17 + - os: macos-13 + test-java-version: 21 + - os: macos-13 + test-java-version: 24 # renovate: datasource=java-version + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - id: setup-java-test + name: Set up Java ${{ matrix.test-java-version }} for tests + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: ${{ matrix.test-java-version }} + + - id: setup-java + name: Set up Java for build + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: 17 + + - name: Set up gradle + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + with: + cache-read-only: ${{ inputs.cache-read-only }} + + - name: Gradle test + run: > + ./gradlew test + "-PtestJavaVersion=${{ matrix.test-java-version }}" + "-Porg.gradle.java.installations.paths=${{ steps.setup-java-test.outputs.path }}" + "-Porg.gradle.java.installations.auto-download=false" + ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + + integration-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up JDK for running Gradle + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: 17 + + - name: Set up gradle + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + with: + cache-read-only: ${{ inputs.cache-read-only }} + + - name: Integration test + run: ./gradlew integrationTest ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + + - name: Save integration test results + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + if: always() + with: + name: integration-test-results + path: jmx-metrics/build/reports/tests/integrationTest + + markdown-lint-check: + uses: ./.github/workflows/reusable-markdown-lint.yml + + misspell-check: + uses: ./.github/workflows/reusable-misspell-check.yml + + shell-script-check: + uses: ./.github/workflows/reusable-shell-script-check.yml diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml new file mode 100644 index 000000000..7704cab52 --- /dev/null +++ b/.github/workflows/build-daily.yml @@ -0,0 +1,35 @@ +name: Build Daily + +on: + workflow_dispatch: + schedule: + # Run daily at 7:30 AM UTC + - cron: '30 7 * * *' + +permissions: + contents: read + +jobs: + common: + uses: ./.github/workflows/build-common.yml + with: + no-build-cache: true + + link-check: + uses: ./.github/workflows/reusable-link-check.yml + + workflow-notification: + permissions: + contents: read + issues: write + if: always() + needs: + - common + - link-check + uses: ./.github/workflows/reusable-workflow-notification.yml + with: + success: >- + ${{ + needs.common.result == 'success' && + needs.link-check.result == 'success' + }} diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml new file mode 100644 index 000000000..d574d5cd4 --- /dev/null +++ b/.github/workflows/build-pull-request.yml @@ -0,0 +1,32 @@ +name: Build Pull Request + +on: + pull_request: + merge_group: + +permissions: + contents: read + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + common: + uses: ./.github/workflows/build-common.yml + with: + cache-read-only: true + + link-check: + uses: ./.github/workflows/reusable-link-check.yml + + required-status-check: + if: always() + needs: + - common + - link-check # wait for link check to complete, but don't require it to pass for merging + runs-on: ubuntu-latest + steps: + # The reusable workflow success depends on all its jobs passing + - if: needs.common.result != 'success' + run: exit 1 # fail diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ceeed192c..2620367bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,156 +5,21 @@ on: branches: - main - release/* - pull_request: - merge_group: - workflow_dispatch: - schedule: - # Run daily at 7:30 AM UTC - - cron: '30 7 * * *' permissions: contents: read -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} - cancel-in-progress: true - jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + common: + uses: ./.github/workflows/build-common.yml - - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - with: - distribution: temurin - java-version: 17 - - - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - with: - cache-read-only: ${{ github.event_name == 'pull_request' }} - - name: Gradle build and test - run: ./gradlew build -x test - - test: - name: Test - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: - - macos-latest - - macos-13 - - ubuntu-latest - - windows-latest - test-java-version: - - 8 - - 11 - - 17 - - 21 - - 24 # renovate: datasource=java-version - # macos-latest drops support for java 8 temurin. Run java 8 on macos-13. Run java 11, 17, 21 on macos-latest. - exclude: - - os: macos-latest - test-java-version: 8 - - os: macos-13 - test-java-version: 11 - - os: macos-13 - test-java-version: 17 - - os: macos-13 - test-java-version: 21 - - os: macos-13 - test-java-version: 24 # renovate: datasource=java-version - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - id: setup-java-test - name: Set up Java ${{ matrix.test-java-version }} for tests - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - with: - distribution: temurin - java-version: ${{ matrix.test-java-version }} - - - id: setup-java - name: Set up Java for build - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - with: - distribution: temurin - java-version: 17 - - - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - with: - cache-read-only: ${{ github.event_name == 'pull_request' }} - - name: Gradle test - run: > - ./gradlew test - "-PtestJavaVersion=${{ matrix.test-java-version }}" - "-Porg.gradle.java.installations.paths=${{ steps.setup-java-test.outputs.path }}" - "-Porg.gradle.java.installations.auto-download=false" - - integration-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 - with: - distribution: temurin - java-version: 17 - - - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - with: - cache-read-only: ${{ github.event_name == 'pull_request' }} - - - name: Integration test - run: ./gradlew integrationTest - - - name: Save integration test results - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: integration-test-results - path: jmx-metrics/build/reports/tests/integrationTest - - link-check: - # merge group and push events are excluded to avoid unnecessary CI failures - # (these failures will instead be captured by the daily scheduled run) - # - # release branches are excluded to avoid unnecessary maintenance if external links break - # (and also because the README.md might need update on release branches before the release - # download has been published) - if: github.event_name != 'merge_group' && github.event_name != 'push' && !startsWith(github.ref_name, 'release/') - uses: ./.github/workflows/reusable-link-check.yml - - markdown-lint-check: - uses: ./.github/workflows/reusable-markdown-lint.yml - - misspell-check: - uses: ./.github/workflows/reusable-misspell-check.yml - - shell-script-check: - uses: ./.github/workflows/reusable-shell-script-check.yml + # Link check is disabled for push events to avoid unnecessary CI failures + # (these failures will instead be captured by the daily scheduled run) + # and for release branches to avoid unnecessary maintenance if external links break publish-snapshots: - # the condition is on the steps below instead of here on the job, because skipping the job - # causes the job to show up as canceled in the GitHub UI which prevents the PR build section - # from collapsing when everything (else) is green - # - # and the name is updated when the steps below are skipped which makes what's happening clearer - # in the GitHub UI - # - # note: the condition below has to be written so that '' is last since it resolves to false - # and so would not short-circuit if used in the second-last position - name: publish-snapshots${{ (github.ref_name != 'main' || github.repository != 'open-telemetry/opentelemetry-java-contrib') && ' (skipped)' || '' }} needs: - # intentionally not blocking snapshot publishing on link-check or misspell-check - - build - - integration-test + - common runs-on: ubuntu-latest steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -167,69 +32,11 @@ jobs: - name: Set up gradle uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - # skipping release branches because the versions in those branches are not snapshots - # (also this skips pull requests) - if: ${{ github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-contrib' }} + - name: Build and publish snapshots - if: ${{ github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-contrib' }} run: ./gradlew assemble publishToSonatype env: SONATYPE_USER: ${{ secrets.SONATYPE_USER }} SONATYPE_KEY: ${{ secrets.SONATYPE_KEY }} GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} - - required-status-check: - if: (github.event_name == 'pull_request' || github.event_name == 'merge_group') && always() - needs: - - build - - test - - integration-test - - markdown-lint-check - - misspell-check - - shell-script-check - runs-on: ubuntu-latest - steps: - # only the build and test checks are required for release branch PRs in order - # to avoid any unnecessary release branch maintenance (especially for patches) - - if: | - needs.build.result != 'success' || - needs.test.result != 'success' || - needs.integration-test.result != 'success' || - ( - !startsWith(github.base_ref, 'release/') && - ( - needs.markdown-lint-check.result != 'success' || - needs.misspell-check.result != 'success' || - needs.shell-script-check.result != 'success' - ) - ) - run: exit 1 # fail - - workflow-notification: - permissions: - contents: read - issues: write - if: (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') && always() - needs: - - build - - test - - integration-test - - link-check - - markdown-lint-check - - misspell-check - - shell-script-check - - publish-snapshots - uses: ./.github/workflows/reusable-workflow-notification.yml - with: - success: >- - ${{ - needs.build.result == 'success' && - needs.test.result == 'success' && - needs.integration-test.result == 'success' && - needs.link-check.result == 'success' && - needs.markdown-lint-check.result == 'success' && - needs.misspell-check.result == 'success' && - needs.shell-script-check.result == 'success' && - needs.publish-snapshots.result == 'success' - }} From 68c257853a6d1c7e20dcb1f425a93f0f0fef5085 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:40:23 +0300 Subject: [PATCH 200/371] chore(deps): update plugin com.gradle.develocity to v4.1.1 (#2139) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index ba79c4693..5502252e7 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.gradleup.shadow") version "9.0.2" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.1" + id("com.gradle.develocity") version "4.1.1" } } From 4897893487e29d03b5004acf4a216740c7c07847 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 20 Aug 2025 10:40:49 -0700 Subject: [PATCH 201/371] Fix merge queue (#2143) --- .github/workflows/build-pull-request.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index d574d5cd4..43827c623 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -8,7 +8,7 @@ permissions: contents: read concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: From a6636bb464fab0fbb86c080f29dfaf6faa1cb986 Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Wed, 20 Aug 2025 19:28:05 +0100 Subject: [PATCH 202/371] Update README.md to remove duplicate doc line (#2141) --- inferred-spans/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/inferred-spans/README.md b/inferred-spans/README.md index 9b32f51af..667f0d201 100644 --- a/inferred-spans/README.md +++ b/inferred-spans/README.md @@ -41,7 +41,6 @@ So if you are using an autoconfigured OpenTelemetry SDK, you'll only need to add | otel.inferred.spans.interval
OTEL_INFERRED_SPANS_INTERVAL | `5s` | The interval at which profiling sessions should be started. | | otel.inferred.spans.duration
OTEL_INFERRED_SPANS_DURATION | `5s` | The duration of a profiling session. For sampled transactions which fall within a profiling session (they start after and end before the session), so-called inferred spans will be created. They appear in the trace waterfall view like regular spans.
NOTE: It is not recommended to set much higher durations as it may fill the activation events file and async-profiler's frame buffer. Warnings will be logged if the activation events file is full. If you want to have more profiling coverage, try decreasing `profiling_inferred_spans_interval` | | otel.inferred.spans.lib.directory
OTEL_INFERRED_SPANS_LIB_DIRECTORY | Defaults to the value of `java.io.tmpdir` | Profiling requires that the [async-profiler](https://github.com/async-profiler/async-profiler) shared library is exported to a temporary location and loaded by the JVM. The partition backing this location must be executable, however in some server-hardened environments, `noexec` may be set on the standard `/tmp` partition, leading to `java.lang.UnsatisfiedLinkError` errors. Set this property to an alternative directory (e.g. `/var/tmp`) to resolve this. | -| otel.inferred.spans.duration
OTEL_INFERRED_SPANS_DURATION | `5s` | The duration of a profiling session. For sampled transactions which fall within a profiling session (they start after and end before the session), so-called inferred spans will be created. They appear in the trace waterfall view like regular spans.
NOTE: It is not recommended to set much higher durations as it may fill the activation events file and async-profiler's frame buffer. Warnings will be logged if the activation events file is full. If you want to have more profiling coverage, try decreasing `profiling_inferred_spans_interval` | | otel.inferred.spans.parent.override.handler
OTEL_INFERRED_SPANS_PARENT_OVERRIDE_HANDLER | Defaults to a handler adding span-links to the inferred span | Inferred spans sometimes need to be inserted as the new parent of a normal span, which is not directly possible because that span has already been sent. For this reason, this relationship needs to be represented differently, which normally is done by adding a span-link to the inferred span. This configuration can be used to override that behaviour by providing the fully qualified name of a class implementing `BiConsumer`: The biconsumer will be invoked with the inferred span as first argument and the span for which the inferred one was detected as new parent as second argument | ### Manual SDK setup From 50fa886a777a44c43671e85b5234097f1df60442 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 20 Aug 2025 13:25:46 -0700 Subject: [PATCH 203/371] Fix daily build (#2142) --- .github/workflows/build-common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index b11fdaa6c..399b063b7 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -90,7 +90,7 @@ jobs: "-PtestJavaVersion=${{ matrix.test-java-version }}" "-Porg.gradle.java.installations.paths=${{ steps.setup-java-test.outputs.path }}" "-Porg.gradle.java.installations.auto-download=false" - ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + ${{ inputs.no-build-cache && '--no-build-cache' || '' }} integration-test: runs-on: ubuntu-latest From 2b8de4b7efcd31b2698c77443781e0d8873eba30 Mon Sep 17 00:00:00 2001 From: Tyler Benson <734411+tylerbenson@users.noreply.github.com> Date: Wed, 20 Aug 2025 16:56:09 -0400 Subject: [PATCH 204/371] Use a more efficient serializer and write bytes directly to disk (#2138) --- disk-buffering/build.gradle.kts | 8 ++-- .../internal/exporter/NoopSerializer.java | 30 +++++++++++++ .../internal/exporter/ToDiskExporter.java | 7 +++- .../exporter/ToDiskExporterBuilder.java | 2 +- .../LogRecordDataDeserializer.java | 5 ++- .../deserializers/MetricDataDeserializer.java | 5 ++- .../deserializers/SpanDataDeserializer.java | 5 ++- .../mapping/common/ByteStringMapper.java | 4 +- .../mapping/logs/ProtoLogsDataMapper.java | 10 ++--- .../metrics/ProtoMetricsDataMapper.java | 10 ++--- .../mapping/spans/ProtoSpansDataMapper.java | 11 ++--- .../serializers/LogRecordDataSerializer.java | 42 +++++++++++-------- .../serializers/MetricDataSerializer.java | 42 +++++++++++-------- .../serializers/SignalSerializer.java | 16 +++++-- .../serializers/SpanDataSerializer.java | 42 +++++++++++-------- .../buffering/internal/storage/Storage.java | 13 +++--- .../internal/storage/files/WritableFile.java | 9 ++-- .../buffering/SpanFromDiskExporterTest.java | 2 +- .../internal/exporter/ToDiskExporterTest.java | 13 +++--- .../mapping/logs/ProtoLogsDataMapperTest.java | 16 +++---- .../metrics/ProtoMetricsDataMapperTest.java | 14 +++---- .../spans/ProtoSpansDataMapperTest.java | 14 +++---- .../serializers/ByteArraySerializer.java | 39 +++++++++++++++++ .../internal/storage/FolderManagerTest.java | 3 +- .../internal/storage/StorageTest.java | 17 ++++---- .../storage/files/ReadableFileTest.java | 13 +++--- .../storage/files/WritableFileTest.java | 19 +++++---- .../testutils/BaseSignalSerializerTest.java | 13 +++++- 28 files changed, 274 insertions(+), 150 deletions(-) create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java create mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/ByteArraySerializer.java diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index d2afee7e2..0a883cf73 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -17,6 +17,7 @@ val protos by configurations.creating dependencies { api("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-api-incubator") + implementation("io.opentelemetry:opentelemetry-exporter-otlp-common") compileOnly("com.google.auto.value:auto-value-annotations") annotationProcessor("com.google.auto.value:auto-value") testImplementation("org.mockito:mockito-inline") @@ -47,9 +48,10 @@ wire { } root( - "opentelemetry.proto.trace.v1.TracesData", - "opentelemetry.proto.metrics.v1.MetricsData", - "opentelemetry.proto.logs.v1.LogsData", + // These are the types used by the Java SDK's OTLP exporters. + "opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest", + "opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest", + "opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest", ) } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java new file mode 100644 index 000000000..715f538c4 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.exporter; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; + +class NoopSerializer implements SignalSerializer { + + @Override + public NoopSerializer initialize(Collection data) { + return this; + } + + @Override + public void writeBinaryTo(OutputStream output) throws IOException {} + + @Override + public int getBinarySerializedSize() { + return 0; + } + + @Override + public void reset() {} +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java index b54e3cc16..5b2dcd186 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java @@ -38,10 +38,11 @@ public static ToDiskExporterBuilder builder(Storage storage) { return new ToDiskExporterBuilder<>(storage); } - public CompletableResultCode export(Collection data) { + public synchronized CompletableResultCode export(Collection data) { logger.log("Intercepting exporter batch.", Level.FINER); try { - if (storage.write(serializer.serialize(data))) { + serializer.initialize(data); + if (storage.write(serializer)) { return CompletableResultCode.ofSuccess(); } logger.log("Could not store batch in disk. Exporting it right away."); @@ -52,6 +53,8 @@ public CompletableResultCode export(Collection data) { Level.WARNING, e); return exportFunction.apply(data); + } finally { + serializer.reset(); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java index 069e08986..be75a3976 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java @@ -14,7 +14,7 @@ public final class ToDiskExporterBuilder { - private SignalSerializer serializer = ts -> new byte[0]; + private SignalSerializer serializer = new NoopSerializer(); private final Storage storage; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java index 5ac0007d9..cbbb4a0ad 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java @@ -7,7 +7,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.ProtoLogsDataMapper; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.proto.logs.v1.LogsData; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.sdk.logs.data.LogRecordData; import java.io.IOException; import java.util.List; @@ -24,7 +24,8 @@ static LogRecordDataDeserializer getInstance() { @Override public List deserialize(byte[] source) throws DeserializationException { try { - return ProtoLogsDataMapper.getInstance().fromProto(LogsData.ADAPTER.decode(source)); + return ProtoLogsDataMapper.getInstance() + .fromProto(ExportLogsServiceRequest.ADAPTER.decode(source)); } catch (IOException e) { throw new DeserializationException(e); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java index 34e88b3ef..d6410d4e7 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java @@ -7,7 +7,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.metrics.ProtoMetricsDataMapper; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.proto.metrics.v1.MetricsData; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.sdk.metrics.data.MetricData; import java.io.IOException; import java.util.List; @@ -24,7 +24,8 @@ static MetricDataDeserializer getInstance() { @Override public List deserialize(byte[] source) throws DeserializationException { try { - return ProtoMetricsDataMapper.getInstance().fromProto(MetricsData.ADAPTER.decode(source)); + return ProtoMetricsDataMapper.getInstance() + .fromProto(ExportMetricsServiceRequest.ADAPTER.decode(source)); } catch (IOException e) { throw new DeserializationException(e); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java index 457d5f268..eb4406ff3 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java @@ -7,7 +7,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.ProtoSpansDataMapper; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.proto.trace.v1.TracesData; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.sdk.trace.data.SpanData; import java.io.IOException; import java.util.List; @@ -24,7 +24,8 @@ static SpanDataDeserializer getInstance() { @Override public List deserialize(byte[] source) throws DeserializationException { try { - return ProtoSpansDataMapper.getInstance().fromProto(TracesData.ADAPTER.decode(source)); + return ProtoSpansDataMapper.getInstance() + .fromProto(ExportTraceServiceRequest.ADAPTER.decode(source)); } catch (IOException e) { throw new DeserializationException(e); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ByteStringMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ByteStringMapper.java index ca8366e8a..1234d25de 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ByteStringMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ByteStringMapper.java @@ -16,10 +16,10 @@ public static ByteStringMapper getInstance() { } public ByteString stringToProto(String source) { - return ByteString.encodeUtf8(source); + return ByteString.decodeHex(source); } public String protoToString(ByteString source) { - return source.utf8(); + return source.hex(); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapper.java index 1d11c177f..021935f9a 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapper.java @@ -6,8 +6,8 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.common.BaseProtoSignalsDataMapper; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.proto.logs.v1.LogRecord; -import io.opentelemetry.proto.logs.v1.LogsData; import io.opentelemetry.proto.logs.v1.ResourceLogs; import io.opentelemetry.proto.logs.v1.ScopeLogs; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -19,7 +19,7 @@ public final class ProtoLogsDataMapper extends BaseProtoSignalsDataMapper< - LogRecordData, LogRecord, LogsData, ResourceLogs, ScopeLogs> { + LogRecordData, LogRecord, ExportLogsServiceRequest, ResourceLogs, ScopeLogs> { private static final ProtoLogsDataMapper INSTANCE = new ProtoLogsDataMapper(); @@ -39,12 +39,12 @@ protected LogRecordData protoToSignalItem( } @Override - protected List getProtoResources(LogsData logsData) { + protected List getProtoResources(ExportLogsServiceRequest logsData) { return logsData.resource_logs; } @Override - protected LogsData createProtoData( + protected ExportLogsServiceRequest createProtoData( Map>> itemsByResource) { List items = new ArrayList<>(); itemsByResource.forEach( @@ -58,7 +58,7 @@ protected LogsData createProtoData( } items.add(resourceLogsBuilder.build()); }); - return new LogsData.Builder().resource_logs(items).build(); + return new ExportLogsServiceRequest.Builder().resource_logs(items).build(); } private ScopeLogs.Builder createProtoScopeBuilder(InstrumentationScopeInfo scopeInfo) { diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapper.java index a81ab9957..ad67eee1c 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapper.java @@ -6,8 +6,8 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.metrics; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.common.BaseProtoSignalsDataMapper; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.metrics.v1.Metric; -import io.opentelemetry.proto.metrics.v1.MetricsData; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; import io.opentelemetry.proto.metrics.v1.ScopeMetrics; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; @@ -19,7 +19,7 @@ public final class ProtoMetricsDataMapper extends BaseProtoSignalsDataMapper< - MetricData, Metric, MetricsData, ResourceMetrics, ScopeMetrics> { + MetricData, Metric, ExportMetricsServiceRequest, ResourceMetrics, ScopeMetrics> { private static final ProtoMetricsDataMapper INSTANCE = new ProtoMetricsDataMapper(); @@ -39,12 +39,12 @@ protected MetricData protoToSignalItem( } @Override - protected List getProtoResources(MetricsData protoData) { + protected List getProtoResources(ExportMetricsServiceRequest protoData) { return protoData.resource_metrics; } @Override - protected MetricsData createProtoData( + protected ExportMetricsServiceRequest createProtoData( Map>> itemsByResource) { List items = new ArrayList<>(); itemsByResource.forEach( @@ -58,7 +58,7 @@ protected MetricsData createProtoData( } items.add(resourceMetricsBuilder.build()); }); - return new MetricsData.Builder().resource_metrics(items).build(); + return new ExportMetricsServiceRequest.Builder().resource_metrics(items).build(); } private ScopeMetrics.Builder createProtoScopeBuilder(InstrumentationScopeInfo scopeInfo) { diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapper.java index 18acf3a1f..12697c49d 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapper.java @@ -6,10 +6,10 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.common.BaseProtoSignalsDataMapper; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; -import io.opentelemetry.proto.trace.v1.TracesData; import io.opentelemetry.sdk.common.InstrumentationScopeInfo; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.data.SpanData; @@ -18,7 +18,8 @@ import java.util.Map; public final class ProtoSpansDataMapper - extends BaseProtoSignalsDataMapper { + extends BaseProtoSignalsDataMapper< + SpanData, Span, ExportTraceServiceRequest, ResourceSpans, ScopeSpans> { private static final ProtoSpansDataMapper INSTANCE = new ProtoSpansDataMapper(); @@ -32,7 +33,7 @@ protected Span signalItemToProto(SpanData sourceData) { } @Override - protected List getProtoResources(TracesData protoData) { + protected List getProtoResources(ExportTraceServiceRequest protoData) { return protoData.resource_spans; } @@ -43,7 +44,7 @@ protected SpanData protoToSignalItem( } @Override - protected TracesData createProtoData( + protected ExportTraceServiceRequest createProtoData( Map>> itemsByResource) { List items = new ArrayList<>(); itemsByResource.forEach( @@ -57,7 +58,7 @@ protected TracesData createProtoData( } items.add(resourceSpansBuilder.build()); }); - return new TracesData.Builder().resource_spans(items).build(); + return new ExportTraceServiceRequest.Builder().resource_spans(items).build(); } @Override diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializer.java index 72c654ffe..19bb1cf93 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/LogRecordDataSerializer.java @@ -5,33 +5,41 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.ProtoLogsDataMapper; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.contrib.disk.buffering.internal.utils.ProtobufTools; -import io.opentelemetry.proto.logs.v1.LogsData; +import io.opentelemetry.exporter.internal.otlp.logs.LowAllocationLogsRequestMarshaler; import io.opentelemetry.sdk.logs.data.LogRecordData; -import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Collection; public final class LogRecordDataSerializer implements SignalSerializer { - private static final LogRecordDataSerializer INSTANCE = new LogRecordDataSerializer(); - private LogRecordDataSerializer() {} + private final LowAllocationLogsRequestMarshaler marshaler = + new LowAllocationLogsRequestMarshaler(); - static LogRecordDataSerializer getInstance() { - return INSTANCE; + LogRecordDataSerializer() {} + + @CanIgnoreReturnValue + @Override + public LogRecordDataSerializer initialize(Collection data) { + marshaler.initialize(data); + return this; + } + + @Override + public void writeBinaryTo(OutputStream output) throws IOException { + ProtobufTools.writeRawVarint32(marshaler.getBinarySerializedSize(), output); + marshaler.writeBinaryTo(output); + } + + @Override + public int getBinarySerializedSize() { + return marshaler.getBinarySerializedSize(); } @Override - public byte[] serialize(Collection logRecordData) { - LogsData proto = ProtoLogsDataMapper.getInstance().toProto(logRecordData); - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - int size = LogsData.ADAPTER.encodedSize(proto); - ProtobufTools.writeRawVarint32(size, out); - proto.encode(out); - return out.toByteArray(); - } catch (IOException e) { - throw new IllegalStateException(e); - } + public void reset() { + marshaler.reset(); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/MetricDataSerializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/MetricDataSerializer.java index 077d4ade5..726b3185d 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/MetricDataSerializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/MetricDataSerializer.java @@ -5,33 +5,41 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.metrics.ProtoMetricsDataMapper; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.contrib.disk.buffering.internal.utils.ProtobufTools; -import io.opentelemetry.proto.metrics.v1.MetricsData; +import io.opentelemetry.exporter.internal.otlp.metrics.LowAllocationMetricsRequestMarshaler; import io.opentelemetry.sdk.metrics.data.MetricData; -import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Collection; public final class MetricDataSerializer implements SignalSerializer { - private static final MetricDataSerializer INSTANCE = new MetricDataSerializer(); - private MetricDataSerializer() {} + private final LowAllocationMetricsRequestMarshaler marshaler = + new LowAllocationMetricsRequestMarshaler(); - static MetricDataSerializer getInstance() { - return INSTANCE; + MetricDataSerializer() {} + + @CanIgnoreReturnValue + @Override + public MetricDataSerializer initialize(Collection data) { + marshaler.initialize(data); + return this; + } + + @Override + public void writeBinaryTo(OutputStream output) throws IOException { + ProtobufTools.writeRawVarint32(marshaler.getBinarySerializedSize(), output); + marshaler.writeBinaryTo(output); + } + + @Override + public int getBinarySerializedSize() { + return marshaler.getBinarySerializedSize(); } @Override - public byte[] serialize(Collection metricData) { - MetricsData proto = ProtoMetricsDataMapper.getInstance().toProto(metricData); - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - int size = MetricsData.ADAPTER.encodedSize(proto); - ProtobufTools.writeRawVarint32(size, out); - proto.encode(out); - return out.toByteArray(); - } catch (IOException e) { - throw new IllegalStateException(e); - } + public void reset() { + marshaler.reset(); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SignalSerializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SignalSerializer.java index c7d7e5c8c..4c306ceb7 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SignalSerializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SignalSerializer.java @@ -8,21 +8,29 @@ import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.IOException; +import java.io.OutputStream; import java.util.Collection; public interface SignalSerializer { static SignalSerializer ofSpans() { - return SpanDataSerializer.getInstance(); + return new SpanDataSerializer(); } static SignalSerializer ofMetrics() { - return MetricDataSerializer.getInstance(); + return new MetricDataSerializer(); } static SignalSerializer ofLogs() { - return LogRecordDataSerializer.getInstance(); + return new LogRecordDataSerializer(); } - byte[] serialize(Collection items); + SignalSerializer initialize(Collection data); + + void writeBinaryTo(OutputStream output) throws IOException; + + int getBinarySerializedSize(); + + void reset(); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SpanDataSerializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SpanDataSerializer.java index 5a26426db..6e3276231 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SpanDataSerializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SpanDataSerializer.java @@ -5,33 +5,41 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.ProtoSpansDataMapper; +import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.contrib.disk.buffering.internal.utils.ProtobufTools; -import io.opentelemetry.proto.trace.v1.TracesData; +import io.opentelemetry.exporter.internal.otlp.traces.LowAllocationTraceRequestMarshaler; import io.opentelemetry.sdk.trace.data.SpanData; -import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.Collection; public final class SpanDataSerializer implements SignalSerializer { - private static final SpanDataSerializer INSTANCE = new SpanDataSerializer(); - private SpanDataSerializer() {} + private final LowAllocationTraceRequestMarshaler marshaler = + new LowAllocationTraceRequestMarshaler(); - static SpanDataSerializer getInstance() { - return INSTANCE; + SpanDataSerializer() {} + + @CanIgnoreReturnValue + @Override + public SpanDataSerializer initialize(Collection data) { + marshaler.initialize(data); + return this; + } + + @Override + public void writeBinaryTo(OutputStream output) throws IOException { + ProtobufTools.writeRawVarint32(marshaler.getBinarySerializedSize(), output); + marshaler.writeBinaryTo(output); + } + + @Override + public int getBinarySerializedSize() { + return marshaler.getBinarySerializedSize(); } @Override - public byte[] serialize(Collection spanData) { - TracesData proto = ProtoSpansDataMapper.getInstance().toProto(spanData); - try (ByteArrayOutputStream out = new ByteArrayOutputStream()) { - int size = TracesData.ADAPTER.encodedSize(proto); - ProtobufTools.writeRawVarint32(size, out); - proto.encode(out); - return out.toByteArray(); - } catch (IOException e) { - throw new IllegalStateException(e); - } + public void reset() { + marshaler.reset(); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java index 73a263490..86b5284ca 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java @@ -8,6 +8,7 @@ import static java.util.logging.Level.WARNING; import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; @@ -49,14 +50,14 @@ public boolean isDebugEnabled() { /** * Attempts to write an item into a writable file. * - * @param item - The data that would be appended to the file. + * @param marshaler - The data that would be appended to the file. * @throws IOException If an unexpected error happens. */ - public boolean write(byte[] item) throws IOException { - return write(item, 1); + public boolean write(SignalSerializer marshaler) throws IOException { + return write(marshaler, 1); } - private boolean write(byte[] item, int attemptNumber) throws IOException { + private boolean write(SignalSerializer marshaler, int attemptNumber) throws IOException { if (isClosed.get()) { logger.log("Refusing to write to storage after being closed."); return false; @@ -69,11 +70,11 @@ private boolean write(byte[] item, int attemptNumber) throws IOException { writableFile = folderManager.createWritableFile(); logger.log("Created new writableFile: " + writableFile); } - WritableResult result = writableFile.append(item); + WritableResult result = writableFile.append(marshaler); if (result != WritableResult.SUCCEEDED) { // Retry with new file writableFile = null; - return write(item, ++attemptNumber); + return write(marshaler, ++attemptNumber); } return true; } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java index 0f3d1d475..ce4e87ddf 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java @@ -8,6 +8,7 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.util.ClockBuddy.nowMillis; import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; import io.opentelemetry.sdk.common.Clock; import java.io.File; @@ -43,9 +44,9 @@ public WritableFile( * reached the configured max size, the file stream is closed with the contents available in the * buffer before attempting to append the new data. * - * @param data - The new data line to add. + * @param marshaler - The new data line to add. */ - public synchronized WritableResult append(byte[] data) throws IOException { + public synchronized WritableResult append(SignalSerializer marshaler) throws IOException { if (isClosed.get()) { return WritableResult.FAILED; } @@ -53,12 +54,12 @@ public synchronized WritableResult append(byte[] data) throws IOException { close(); return WritableResult.FAILED; } - int futureSize = size + data.length; + int futureSize = size + marshaler.getBinarySerializedSize(); if (futureSize > configuration.getMaxFileSize()) { close(); return WritableResult.FAILED; } - out.write(data); + marshaler.writeBinaryTo(out); size = futureSize; return WritableResult.SUCCEEDED; } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java index ae503ecdf..2ea0d2b8a 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java @@ -86,7 +86,7 @@ private static List writeSomeSpans(Storage storage) throws Exception { SpanData span2 = makeSpan2(TraceFlags.getSampled(), now); List spans = Arrays.asList(span1, span2); - storage.write(SignalSerializer.ofSpans().serialize(spans)); + storage.write(SignalSerializer.ofSpans().initialize(spans)); storage.flush(); return spans; } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java index f7b6e3ff6..0a98061ac 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java @@ -5,8 +5,8 @@ package io.opentelemetry.contrib.disk.buffering.internal.exporter; -import static java.nio.charset.StandardCharsets.UTF_8; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,8 +30,6 @@ class ToDiskExporterTest { private final List records = Arrays.asList("one", "two", "three"); - private final byte[] serialized = "one,two,three".getBytes(UTF_8); - @Mock private SignalSerializer serializer; @Mock private Storage storage; @@ -50,21 +48,20 @@ void setup() { return exportFnResultToReturn.get(); }; toDiskExporter = new ToDiskExporter<>(serializer, exportFn, storage); - when(serializer.serialize(records)).thenReturn(serialized); } @Test void whenWritingSucceedsOnExport_returnSuccessfulResultCode() throws Exception { - when(storage.write(serialized)).thenReturn(true); + when(storage.write(any())).thenReturn(true); CompletableResultCode completableResultCode = toDiskExporter.export(records); assertThat(completableResultCode.isSuccess()).isTrue(); - verify(storage).write(serialized); + verify(storage).write(any()); assertThat(exportedFnSeen).isNull(); } @Test void whenWritingFailsOnExport_doExportRightAway() throws Exception { - when(storage.write(serialized)).thenReturn(false); + when(storage.write(any())).thenReturn(false); exportFnResultToReturn.set(CompletableResultCode.ofSuccess()); CompletableResultCode completableResultCode = toDiskExporter.export(records); @@ -75,7 +72,7 @@ void whenWritingFailsOnExport_doExportRightAway() throws Exception { @Test void whenExceptionInWrite_doExportRightAway() throws Exception { - when(storage.write(serialized)).thenThrow(new IOException("boom")); + when(storage.write(any())).thenThrow(new IOException("boom")); exportFnResultToReturn.set(CompletableResultCode.ofFailure()); CompletableResultCode completableResultCode = toDiskExporter.export(records); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java index 26c73502e..9a5d93cd8 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java @@ -12,8 +12,8 @@ import io.opentelemetry.api.logs.Severity; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.models.LogRecordDataImpl; import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.proto.logs.v1.LogRecord; -import io.opentelemetry.proto.logs.v1.LogsData; import io.opentelemetry.proto.logs.v1.ResourceLogs; import io.opentelemetry.proto.logs.v1.ScopeLogs; import io.opentelemetry.sdk.logs.data.LogRecordData; @@ -104,7 +104,7 @@ class ProtoLogsDataMapperTest { void verifyConversionDataStructure() { List signals = Collections.singletonList(LOG_RECORD); - LogsData result = mapToProto(signals); + ExportLogsServiceRequest result = mapToProto(signals); List resourceLogsList = result.resource_logs; assertEquals(1, resourceLogsList.size()); @@ -118,7 +118,7 @@ void verifyConversionDataStructure() { void verifyMultipleLogsWithSameResourceAndScope() { List signals = Arrays.asList(LOG_RECORD, OTHER_LOG_RECORD); - LogsData proto = mapToProto(signals); + ExportLogsServiceRequest proto = mapToProto(signals); List resourceLogsList = proto.resource_logs; assertEquals(1, resourceLogsList.size()); @@ -139,7 +139,7 @@ void verifyMultipleLogsWithSameResourceDifferentScope() { List signals = Arrays.asList(LOG_RECORD, LOG_RECORD_WITH_DIFFERENT_SCOPE_SAME_RESOURCE); - LogsData proto = mapToProto(signals); + ExportLogsServiceRequest proto = mapToProto(signals); List resourceLogsList = proto.resource_logs; assertEquals(1, resourceLogsList.size()); @@ -159,7 +159,7 @@ void verifyMultipleLogsWithSameResourceDifferentScope() { void verifyMultipleLogsWithDifferentResource() { List signals = Arrays.asList(LOG_RECORD, LOG_RECORD_WITH_DIFFERENT_RESOURCE); - LogsData proto = mapToProto(signals); + ExportLogsServiceRequest proto = mapToProto(signals); List resourceLogsList = proto.resource_logs; assertEquals(2, resourceLogsList.size()); @@ -183,7 +183,7 @@ void verifyMultipleLogsWithDifferentResource() { void verifyLogWithEventName() { List signals = Collections.singletonList(LOG_RECORD_WITH_EVENT_NAME); - LogsData result = mapToProto(signals); + ExportLogsServiceRequest result = mapToProto(signals); List resourceLogsList = result.resource_logs; LogRecord firstLog = resourceLogsList.get(0).scope_logs.get(0).log_records.get(0); @@ -192,11 +192,11 @@ void verifyLogWithEventName() { assertThat(mapFromProto(result)).containsExactlyInAnyOrderElementsOf(signals); } - private static LogsData mapToProto(Collection signals) { + private static ExportLogsServiceRequest mapToProto(Collection signals) { return ProtoLogsDataMapper.getInstance().toProto(signals); } - private static List mapFromProto(LogsData protoData) { + private static List mapFromProto(ExportLogsServiceRequest protoData) { return ProtoLogsDataMapper.getInstance().fromProto(protoData); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java index b45e9c9e7..59d369704 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java @@ -10,8 +10,8 @@ import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.proto.metrics.v1.Metric; -import io.opentelemetry.proto.metrics.v1.MetricsData; import io.opentelemetry.proto.metrics.v1.ResourceMetrics; import io.opentelemetry.proto.metrics.v1.ScopeMetrics; import io.opentelemetry.sdk.metrics.data.MetricData; @@ -30,7 +30,7 @@ void verifyConversionDataStructure() { MetricData expectedGauge1 = TestData.makeLongGauge(TraceFlags.getSampled()); List expectedSignals = Collections.singletonList(expectedGauge1); - MetricsData proto = mapToProto(signals); + ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; assertEquals(1, resourceMetrics.size()); @@ -49,7 +49,7 @@ void verifyMultipleMetricsWithSameResourceAndScope() { MetricData expectedGauge2 = TestData.makeLongGauge(TraceFlags.getSampled()); List expectedSignals = Arrays.asList(expectedGauge1, expectedGauge2); - MetricsData proto = mapToProto(signals); + ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; assertEquals(1, resourceMetrics.size()); @@ -78,7 +78,7 @@ void verifyMultipleMetricsWithSameResourceDifferentScope() { List signals = Arrays.asList(gauge1, gauge2); List expectedSignals = Arrays.asList(expectedGauge1, expectedGauge2); - MetricsData proto = mapToProto(signals); + ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; assertEquals(1, resourceMetrics.size()); @@ -113,7 +113,7 @@ void verifyMultipleMetricsWithDifferentResource() { // , LONG_GAUGE_METRIC_WITH_DIFFERENT_RESOURCE); // List expectedSignals = Arrays.asList(expected); - MetricsData proto = mapToProto(signals); + ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; assertEquals(2, resourceMetrics.size()); @@ -133,11 +133,11 @@ void verifyMultipleMetricsWithDifferentResource() { assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(expectedSignals); } - private static MetricsData mapToProto(Collection signals) { + private static ExportMetricsServiceRequest mapToProto(Collection signals) { return ProtoMetricsDataMapper.getInstance().toProto(signals); } - private static List mapFromProto(MetricsData protoData) { + private static List mapFromProto(ExportMetricsServiceRequest protoData) { return ProtoMetricsDataMapper.getInstance().fromProto(protoData); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java index bdd9c053c..ca325496b 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java @@ -11,10 +11,10 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.trace.v1.ResourceSpans; import io.opentelemetry.proto.trace.v1.ScopeSpans; import io.opentelemetry.proto.trace.v1.Span; -import io.opentelemetry.proto.trace.v1.TracesData; import io.opentelemetry.sdk.trace.data.EventData; import io.opentelemetry.sdk.trace.data.LinkData; import io.opentelemetry.sdk.trace.data.SpanData; @@ -116,7 +116,7 @@ class ProtoSpansDataMapperTest { void verifyConversionDataStructure() { List signals = Collections.singletonList(SPAN_DATA); - TracesData proto = mapToProto(signals); + ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; assertEquals(1, resourceSpans.size()); @@ -130,7 +130,7 @@ void verifyConversionDataStructure() { void verifyMultipleSpansWithSameResourceAndScope() { List signals = Arrays.asList(SPAN_DATA, OTHER_SPAN_DATA); - TracesData proto = mapToProto(signals); + ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; assertEquals(1, resourceSpans.size()); @@ -146,7 +146,7 @@ void verifyMultipleSpansWithSameResourceAndScope() { void verifyMultipleSpansWithSameResourceDifferentScope() { List signals = Arrays.asList(SPAN_DATA, SPAN_DATA_WITH_DIFFERENT_SCOPE_SAME_RESOURCE); - TracesData proto = mapToProto(signals); + ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; assertEquals(1, resourceSpans.size()); @@ -166,7 +166,7 @@ void verifyMultipleSpansWithSameResourceDifferentScope() { void verifyMultipleSpansWithDifferentResource() { List signals = Arrays.asList(SPAN_DATA, SPAN_DATA_WITH_DIFFERENT_RESOURCE); - TracesData proto = mapToProto(signals); + ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; assertEquals(2, resourceSpans.size()); @@ -186,11 +186,11 @@ void verifyMultipleSpansWithDifferentResource() { assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } - private static TracesData mapToProto(Collection signals) { + private static ExportTraceServiceRequest mapToProto(Collection signals) { return ProtoSpansDataMapper.getInstance().toProto(signals); } - private static List mapFromProto(TracesData protoData) { + private static List mapFromProto(ExportTraceServiceRequest protoData) { return ProtoSpansDataMapper.getInstance().fromProto(protoData); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/ByteArraySerializer.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/ByteArraySerializer.java new file mode 100644 index 000000000..7ad446729 --- /dev/null +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/ByteArraySerializer.java @@ -0,0 +1,39 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.IOException; +import java.io.OutputStream; +import java.util.Collection; + +public final class ByteArraySerializer implements SignalSerializer { + + private final byte[] data; + + public ByteArraySerializer(byte[] data) { + this.data = data; + } + + @CanIgnoreReturnValue + @Override + public SignalSerializer initialize(Collection data) { + return null; + } + + @Override + public void writeBinaryTo(OutputStream output) throws IOException { + output.write(data); + } + + @Override + public int getBinarySerializedSize() { + return data.length; + } + + @Override + public void reset() {} +} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java index 5b72c29e8..ad994c38d 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java @@ -18,6 +18,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.ByteArraySerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; import io.opentelemetry.sdk.common.Clock; @@ -83,7 +84,7 @@ void closeCurrentlyWritableFile_whenItIsReadyToBeRead_andNoOtherReadableFilesAre when(clock.now()).thenReturn(MILLISECONDS.toNanos(createdFileTime)); WritableFile writableFile = folderManager.createWritableFile(); - writableFile.append(new byte[3]); + writableFile.append(new ByteArraySerializer(new byte[3])); when(clock.now()) .thenReturn(MILLISECONDS.toNanos(createdFileTime + MIN_FILE_AGE_FOR_READ_MILLIS)); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java index 7f134afdc..d96b9a1bc 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java @@ -17,6 +17,7 @@ import static org.mockito.Mockito.when; import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.ByteArraySerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; @@ -87,7 +88,7 @@ void whenReadingMultipleTimes_reuseReader() throws IOException { @Test void whenWritingMultipleTimes_reuseWriter() throws IOException { - byte[] data = new byte[1]; + ByteArraySerializer data = new ByteArraySerializer(new byte[1]); WritableFile anotherWriter = createWritableFile(); when(folderManager.createWritableFile()).thenReturn(writableFile).thenReturn(anotherWriter); @@ -108,7 +109,7 @@ void whenAttemptingToReadAfterClosed_returnFailed() throws IOException { @Test void whenAttemptingToWriteAfterClosed_returnFalse() throws IOException { storage.close(); - assertFalse(storage.write(new byte[1])); + assertFalse(storage.write(new ByteArraySerializer(new byte[1]))); } @Test @@ -159,7 +160,7 @@ void whenEveryNewFileFoundCannotBeRead_returnContentNotAvailable() throws IOExce @Test void appendDataToFile() throws IOException { when(folderManager.createWritableFile()).thenReturn(writableFile); - byte[] data = new byte[1]; + ByteArraySerializer data = new ByteArraySerializer(new byte[1]); storage.write(data); @@ -168,7 +169,7 @@ void appendDataToFile() throws IOException { @Test void whenWritingTimeoutHappens_retryWithNewFile() throws IOException { - byte[] data = new byte[1]; + ByteArraySerializer data = new ByteArraySerializer(new byte[1]); WritableFile workingWritableFile = createWritableFile(); when(folderManager.createWritableFile()) .thenReturn(writableFile) @@ -182,7 +183,7 @@ void whenWritingTimeoutHappens_retryWithNewFile() throws IOException { @Test void whenThereIsNoSpaceAvailableForWriting_retryWithNewFile() throws IOException { - byte[] data = new byte[1]; + ByteArraySerializer data = new ByteArraySerializer(new byte[1]); WritableFile workingWritableFile = createWritableFile(); when(folderManager.createWritableFile()) .thenReturn(writableFile) @@ -196,7 +197,7 @@ void whenThereIsNoSpaceAvailableForWriting_retryWithNewFile() throws IOException @Test void whenWritingResourceIsClosed_retryWithNewFile() throws IOException { - byte[] data = new byte[1]; + ByteArraySerializer data = new ByteArraySerializer(new byte[1]); WritableFile workingWritableFile = createWritableFile(); when(folderManager.createWritableFile()) .thenReturn(writableFile) @@ -210,7 +211,7 @@ void whenWritingResourceIsClosed_retryWithNewFile() throws IOException { @Test void whenEveryAttemptToWriteFails_returnFalse() throws IOException { - byte[] data = new byte[1]; + ByteArraySerializer data = new ByteArraySerializer(new byte[1]); when(folderManager.createWritableFile()).thenReturn(writableFile); when(writableFile.append(data)).thenReturn(WritableResult.FAILED); @@ -223,7 +224,7 @@ void whenEveryAttemptToWriteFails_returnFalse() throws IOException { void whenClosing_closeWriterAndReaderIfNotNull() throws IOException { when(folderManager.createWritableFile()).thenReturn(writableFile); when(folderManager.getReadableFile()).thenReturn(readableFile); - storage.write(new byte[1]); + storage.write(new ByteArraySerializer(new byte[1])); storage.readAndProcess(processing); storage.close(); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java index a94b5fb3d..a9d0eb5da 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java @@ -30,6 +30,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import org.junit.jupiter.api.AfterEach; @@ -105,14 +106,12 @@ void tearDown() throws IOException { } private static void addFileContents(File source) throws IOException { - List items = new ArrayList<>(); - items.add(SERIALIZER.serialize(Collections.singleton(FIRST_LOG_RECORD))); - items.add(SERIALIZER.serialize(Collections.singleton(SECOND_LOG_RECORD))); - items.add(SERIALIZER.serialize(Collections.singleton(THIRD_LOG_RECORD))); - try (FileOutputStream out = new FileOutputStream(source)) { - for (byte[] item : items) { - out.write(item); + for (LogRecordData item : + Arrays.asList(FIRST_LOG_RECORD, SECOND_LOG_RECORD, THIRD_LOG_RECORD)) { + SERIALIZER.initialize(Collections.singleton(item)); + SERIALIZER.writeBinaryTo(out); + SERIALIZER.reset(); } } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java index 3df37eb4c..cae1e9f64 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java @@ -14,6 +14,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.ByteArraySerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.TestData; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; import io.opentelemetry.sdk.common.Clock; @@ -71,8 +72,8 @@ void hasExpired_whenWriteAgeHasExpired() { void appendDataInNewLines_andIncreaseSize() throws IOException { byte[] line1 = getByteArrayLine("First line"); byte[] line2 = getByteArrayLine("Second line"); - writableFile.append(line1); - writableFile.append(line2); + writableFile.append(new ByteArraySerializer(line1)); + writableFile.append(new ByteArraySerializer(line2)); writableFile.close(); List lines = getWrittenLines(); @@ -85,9 +86,11 @@ void appendDataInNewLines_andIncreaseSize() throws IOException { @Test void whenAppendingData_andNotEnoughSpaceIsAvailable_closeAndReturnFailed() throws IOException { - assertEquals(WritableResult.SUCCEEDED, writableFile.append(new byte[MAX_FILE_SIZE])); + assertEquals( + WritableResult.SUCCEEDED, + writableFile.append(new ByteArraySerializer(new byte[MAX_FILE_SIZE]))); - assertEquals(WritableResult.FAILED, writableFile.append(new byte[1])); + assertEquals(WritableResult.FAILED, writableFile.append(new ByteArraySerializer(new byte[1]))); assertEquals(1, getWrittenLines().size()); assertEquals(MAX_FILE_SIZE, writableFile.getSize()); @@ -95,21 +98,21 @@ void whenAppendingData_andNotEnoughSpaceIsAvailable_closeAndReturnFailed() throw @Test void whenAppendingData_andHasExpired_closeAndReturnExpiredStatus() throws IOException { - writableFile.append(new byte[2]); + writableFile.append(new ByteArraySerializer(new byte[2])); when(clock.now()) .thenReturn(MILLISECONDS.toNanos(CREATED_TIME_MILLIS + MAX_FILE_AGE_FOR_WRITE_MILLIS)); - assertEquals(WritableResult.FAILED, writableFile.append(new byte[1])); + assertEquals(WritableResult.FAILED, writableFile.append(new ByteArraySerializer(new byte[1]))); assertEquals(1, getWrittenLines().size()); } @Test void whenAppendingData_andIsAlreadyClosed_returnFailedStatus() throws IOException { - writableFile.append(new byte[1]); + writableFile.append(new ByteArraySerializer(new byte[1])); writableFile.close(); - assertEquals(WritableResult.FAILED, writableFile.append(new byte[2])); + assertEquals(WritableResult.FAILED, writableFile.append(new ByteArraySerializer(new byte[2]))); } private static byte[] getByteArrayLine(String line) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java index adfc8fb2f..69186f812 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java @@ -12,6 +12,7 @@ import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.DelimitedProtoStreamReader; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.StreamReader; import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.List; @@ -20,7 +21,17 @@ @SuppressWarnings("unchecked") public abstract class BaseSignalSerializerTest { protected byte[] serialize(SIGNAL_SDK_ITEM... items) { - return getSerializer().serialize(Arrays.asList(items)); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + SignalSerializer serializer = getSerializer(); + try { + serializer.initialize(Arrays.asList(items)); + serializer.writeBinaryTo(byteArrayOutputStream); + } catch (IOException e) { + throw new RuntimeException(e); + } finally { + serializer.reset(); + } + return byteArrayOutputStream.toByteArray(); } protected List deserialize(byte[] source) { From 4c76654f0c3d69ec07ffbcc856a5890da19d7ae6 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 21 Aug 2025 09:59:20 -0700 Subject: [PATCH 205/371] Retry in merge queue (#2146) --- .github/workflows/build-common.yml | 7 ++++++- .github/workflows/build-pull-request.yml | 2 ++ buildSrc/build.gradle.kts | 1 + buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 6 ++++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 399b063b7..e66619ec9 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -9,6 +9,10 @@ on: no-build-cache: type: boolean required: false + max-test-retries: + type: number + required: false + default: 0 permissions: contents: read @@ -90,6 +94,7 @@ jobs: "-PtestJavaVersion=${{ matrix.test-java-version }}" "-Porg.gradle.java.installations.paths=${{ steps.setup-java-test.outputs.path }}" "-Porg.gradle.java.installations.auto-download=false" + "-PmaxTestRetries=${{ inputs.max-test-retries }}" ${{ inputs.no-build-cache && '--no-build-cache' || '' }} integration-test: @@ -109,7 +114,7 @@ jobs: cache-read-only: ${{ inputs.cache-read-only }} - name: Integration test - run: ./gradlew integrationTest ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + run: ./gradlew integrationTest "-PmaxTestRetries=${{ inputs.max-test-retries }}" ${{ inputs.no-build-cache && '--no-build-cache' || '' }} - name: Save integration test results uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 diff --git a/.github/workflows/build-pull-request.yml b/.github/workflows/build-pull-request.yml index 43827c623..d41d87f5f 100644 --- a/.github/workflows/build-pull-request.yml +++ b/.github/workflows/build-pull-request.yml @@ -16,6 +16,8 @@ jobs: uses: ./.github/workflows/build-common.yml with: cache-read-only: true + # retry in merge queue to avoid unnecessary failures + max-test-retries: ${{ github.event_name == 'merge_group' && 5 || 0 }} link-check: uses: ./.github/workflows/reusable-link-check.yml diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index bf473fff1..6de6f8e1e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -17,6 +17,7 @@ dependencies { implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") implementation("org.owasp:dependency-check-gradle:12.1.3") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") + implementation("com.gradle:develocity-gradle-plugin:4.1.1") } spotless { diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 17cc114df..ecdd63bd9 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -66,6 +66,12 @@ tasks { withType().configureEach { useJUnitPlatform() + val maxTestRetries = gradle.startParameter.projectProperties["maxTestRetries"]?.toInt() ?: 0 + develocity.testRetry { + // You can see tests that were retried by this mechanism in the collected test reports and build scans. + maxRetries.set(maxTestRetries) + } + testLogging { exceptionFormat = TestExceptionFormat.FULL showStandardStreams = true From 8e8ab53e0eb5f711ea8bb4ddde763096e59c4add Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 21 Aug 2025 19:53:56 -0700 Subject: [PATCH 206/371] fix(deps): update all patch versions (#2154) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index b061a35d8..63d475c02 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Initialize CodeQL - uses: github/codeql-action/init@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 + uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 + uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index e31e80969..0c8d3af84 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@96f518a34f7a870018057716cc4d7a5c014bd61c # v3.29.10 + uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 with: sarif_file: results.sarif diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index 5417ae121..d1c65f6d0 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.4 + 3.5.5 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index 4f8beee2c..e46320f27 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.4 + 3.5.5 io.opentelemetry.contrib.maven.test From 40bfdac71c91c6676805df90cd49c573bfb442d6 Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Fri, 22 Aug 2025 04:24:19 +0100 Subject: [PATCH 207/371] add updateable threshold sampler (#2137) --- .../ConsistentFixedThresholdSampler.java | 50 +--------------- .../consistent56/ConsistentSampler.java | 11 ++++ .../ConsistentThresholdSampler.java | 60 +++++++++++++++++++ .../ConsistentVariableThresholdSampler.java | 56 +++++++++++++++++ ...onsistentVariableThresholdSamplerTest.java | 49 +++++++++++++++ 5 files changed, 179 insertions(+), 47 deletions(-) create mode 100644 consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentThresholdSampler.java create mode 100644 consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSampler.java create mode 100644 consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSamplerTest.java diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java index 82011e7a0..253edf709 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSampler.java @@ -5,19 +5,9 @@ package io.opentelemetry.contrib.sampler.consistent56; -import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateSamplingProbability; import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; -import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.checkThreshold; -import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; -import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.context.Context; -import io.opentelemetry.sdk.trace.data.LinkData; -import java.util.List; - -public class ConsistentFixedThresholdSampler extends ConsistentSampler { +public class ConsistentFixedThresholdSampler extends ConsistentThresholdSampler { private final long threshold; private final String description; @@ -31,47 +21,13 @@ protected ConsistentFixedThresholdSampler(double samplingProbability) { this(calculateThreshold(samplingProbability)); } - private static long getThreshold(long threshold) { - checkThreshold(threshold); - return threshold; - } - - private static String getThresholdDescription(long threshold) { - String thresholdString; - if (threshold == getMaxThreshold()) { - thresholdString = "max"; - } else { - thresholdString = - ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( - new StringBuilder(), threshold) - .toString(); - } - - return "ConsistentFixedThresholdSampler{threshold=" - + thresholdString - + ", sampling probability=" - + calculateSamplingProbability(threshold) - + "}"; - } - @Override public String getDescription() { return description; } @Override - public SamplingIntent getSamplingIntent( - Context parentContext, - String name, - SpanKind spanKind, - Attributes attributes, - List parentLinks) { - - return () -> { - if (threshold == getMaxThreshold()) { - return getInvalidThreshold(); - } - return threshold; - }; + public long getThreshold() { + return threshold; } } diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java index 8ac616963..22ee83b8c 100644 --- a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSampler.java @@ -55,6 +55,17 @@ public static ConsistentSampler probabilityBased(double samplingProbability) { return new ConsistentFixedThresholdSampler(threshold); } + /** + * Returns a {@link ConsistentSampler} that samples each span with a known probability, where the + * probablity can be dynamically updated. + * + * @param samplingProbability the sampling probability + * @return a sampler + */ + public static ConsistentSampler updateableProbabilityBased(double samplingProbability) { + return new ConsistentVariableThresholdSampler(samplingProbability); + } + /** * Returns a new {@link ConsistentSampler} that respects the sampling decision of the parent span * or falls-back to the given sampler if it is a root span. diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentThresholdSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentThresholdSampler.java new file mode 100644 index 000000000..63c1dbeaa --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentThresholdSampler.java @@ -0,0 +1,60 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateSamplingProbability; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.checkThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getInvalidThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.context.Context; +import io.opentelemetry.sdk.trace.data.LinkData; +import java.util.List; + +public abstract class ConsistentThresholdSampler extends ConsistentSampler { + + protected abstract long getThreshold(); + + protected static long getThreshold(long threshold) { + checkThreshold(threshold); + return threshold; + } + + protected static String getThresholdDescription(long threshold) { + String thresholdString; + if (threshold == getMaxThreshold()) { + thresholdString = "max"; + } else { + thresholdString = + appendLast56BitHexEncodedWithoutTrailingZeros(new StringBuilder(), threshold).toString(); + } + + return "ConsistentFixedThresholdSampler{threshold=" + + thresholdString + + ", sampling probability=" + + calculateSamplingProbability(threshold) + + "}"; + } + + @Override + public SamplingIntent getSamplingIntent( + Context parentContext, + String name, + SpanKind spanKind, + Attributes attributes, + List parentLinks) { + + return () -> { + if (getThreshold() == getMaxThreshold()) { + return getInvalidThreshold(); + } + return getThreshold(); + }; + } +} diff --git a/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSampler.java b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSampler.java new file mode 100644 index 000000000..1558e961c --- /dev/null +++ b/consistent-sampling/src/main/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSampler.java @@ -0,0 +1,56 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateSamplingProbability; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.checkThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; + +public class ConsistentVariableThresholdSampler extends ConsistentThresholdSampler { + + private volatile long threshold; + private volatile String description = ""; + + protected ConsistentVariableThresholdSampler(double samplingProbability) { + setSamplingProbability(samplingProbability); + } + + @Override + public String getDescription() { + return description; + } + + @Override + public long getThreshold() { + return threshold; + } + + public void setSamplingProbability(double samplingProbability) { + long threshold = calculateThreshold(samplingProbability); + checkThreshold(threshold); + this.threshold = threshold; + + String thresholdString; + if (threshold == getMaxThreshold()) { + thresholdString = "max"; + } else { + thresholdString = + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), threshold) + .toString(); + } + + // tiny eventual consistency where the description would be out of date with the threshold, + // but this doesn't really matter + this.description = + "ConsistentVariableThresholdSampler{threshold=" + + thresholdString + + ", sampling probability=" + + calculateSamplingProbability(threshold) + + "}"; + } +} diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSamplerTest.java new file mode 100644 index 000000000..90428fad9 --- /dev/null +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentVariableThresholdSamplerTest.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.sampler.consistent56; + +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.calculateThreshold; +import static io.opentelemetry.contrib.sampler.consistent56.ConsistentSamplingUtil.getMaxThreshold; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class ConsistentVariableThresholdSamplerTest { + + @Test + void testSetSamplingProbability() { + double probability = 0.5; + ConsistentVariableThresholdSampler sampler = + new ConsistentVariableThresholdSampler(probability); + testSetSamplingProbability(probability, sampler, /* updateProbability= */ false); + testSetSamplingProbability(0.25, sampler, /* updateProbability= */ true); + testSetSamplingProbability(0.0, sampler, /* updateProbability= */ true); + testSetSamplingProbability(1.0, sampler, /* updateProbability= */ true); + } + + private static void testSetSamplingProbability( + double probability, ConsistentVariableThresholdSampler sampler, boolean updateProbability) { + long threshold = calculateThreshold(probability); + String thresholdString = + ConsistentSamplingUtil.appendLast56BitHexEncodedWithoutTrailingZeros( + new StringBuilder(), threshold) + .toString(); + if (threshold == getMaxThreshold()) { + thresholdString = "max"; + } + if (updateProbability) { + sampler.setSamplingProbability(probability); + } + assertThat(sampler.getThreshold()).isEqualTo(threshold); + assertThat(sampler.getDescription()) + .isEqualTo( + "ConsistentVariableThresholdSampler{threshold=" + + thresholdString + + ", sampling probability=" + + probability + + "}"); + } +} From e5035846d165d06580230ba265e095908c2b9ce8 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 21 Aug 2025 22:15:13 -0700 Subject: [PATCH 208/371] Fix sporadic CI failures (#2148) --- .../src/main/kotlin/otel.java-conventions.gradle.kts | 7 +++++++ consistent-sampling/build.gradle.kts | 11 +++++++++++ jmx-scraper/build.gradle.kts | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index ecdd63bd9..e3639a9d5 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -76,6 +76,13 @@ tasks { exceptionFormat = TestExceptionFormat.FULL showStandardStreams = true } + + configure { + // only care about code coverage for code in this repository + // (in particular avoiding netty classes which sometimes end up + // causing sporadic CI failures) + includes = listOf("io/opentelemetry/contrib/**") + } } withType().configureEach { diff --git a/consistent-sampling/build.gradle.kts b/consistent-sampling/build.gradle.kts index 88cdf543a..66fca90c9 100644 --- a/consistent-sampling/build.gradle.kts +++ b/consistent-sampling/build.gradle.kts @@ -12,3 +12,14 @@ dependencies { testImplementation("org.hipparchus:hipparchus-core:4.0.1") testImplementation("org.hipparchus:hipparchus-stat:4.0.1") } + +tasks { + withType().configureEach { + develocity.testRetry { + // TODO (trask) fix flaky tests and remove this workaround + if (System.getenv().containsKey("CI")) { + maxRetries.set(5) + } + } + } +} diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 061fdd03e..9e2878549 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -82,7 +82,7 @@ tasks { systemProperty("gradle.project.version", "${project.version}") develocity.testRetry { - // You can see tests that were retried by this mechanism in the collected test reports and build scans. + // TODO (trask) fix flaky tests and remove this workaround if (System.getenv().containsKey("CI")) { maxRetries.set(5) } From cbcfa9261a774197e25181da74920310204bb40f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 22 Aug 2025 08:20:20 -0700 Subject: [PATCH 209/371] Update change log for upcoming release (#2160) --- .github/scripts/draft-change-log-entries.sh | 2 ++ CHANGELOG.md | 25 +++++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/.github/scripts/draft-change-log-entries.sh b/.github/scripts/draft-change-log-entries.sh index 845c92672..489c1fe4d 100755 --- a/.github/scripts/draft-change-log-entries.sh +++ b/.github/scripts/draft-change-log-entries.sh @@ -35,6 +35,7 @@ component_names["consistent-sampling/"]="Consistent sampling" component_names["disk-buffering/"]="Disk buffering" component_names["gcp-resources/"]="GCP resources" component_names["gcp-auth-extension/"]="GCP authentication extension" +component_names["ibm-mq-metrics/"]="IBM MQ metrics" component_names["inferred-spans/"]="Inferred spans" component_names["jfr-connection/"]="JFR connection" component_names["jfr-events/"]="JFR events" @@ -44,6 +45,7 @@ component_names["kafka-exporter/"]="Kafka exporter" component_names["maven-extension/"]="Maven extension" component_names["micrometer-meter-provider/"]="Micrometer MeterProvider" component_names["noop-api/"]="No-op API" +component_names["opamp-client/"]="OpAMP client" component_names["processors/"]="Telemetry processors" component_names["prometheus-client-bridge/"]="Prometheus client bridge" component_names["runtime-attach/"]="Runtime attach" diff --git a/CHANGELOG.md b/CHANGELOG.md index e6744eff5..fa0d0833e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,31 @@ ## Unreleased +### Consistent sampling + +- Add updateable threshold sampler for dynamic sampling configuration + ([#2137](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2137)) + +### Disk buffering + +- Introduce API changes for improved disk buffering functionality + ([#2084](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2084)) +- Implement more efficient serializer with direct disk write capabilities + ([#2138](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2138)) + +### IBM MQ metrics - New 🌟 + +IBM MQ metrics collection utility. + +### Inferred spans + +- Update async profiler to version 4.1 for improved performance + ([#2096](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2096)) + +### OpAMP client - New 🌟 + +OpenTelemetry Agent Management Protocol (OpAMP) client implementation. + ## Version 1.48.0 (2025-07-23) ### AWS resources From 2ee076821e45c57b69373d3fef022a955430b8c5 Mon Sep 17 00:00:00 2001 From: Jacek Pietras <71635976+JacekPietrasSpotOn@users.noreply.github.com> Date: Fri, 22 Aug 2025 17:55:43 +0200 Subject: [PATCH 210/371] Catching IllegalStateException in case of failed deserialization (#2157) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .../LogRecordDataDeserializer.java | 2 +- .../deserializers/MetricDataDeserializer.java | 2 +- .../deserializers/SpanDataDeserializer.java | 2 +- .../LogRecordDataDeserializerTest.java | 63 +++++++++++++++++ .../MetricDataDeserializerTest.java | 41 +++++++++++ .../SpanDataDeserializerTest.java | 68 +++++++++++++++++++ .../disk/buffering/testutils/TestData.java | 19 ++++++ 7 files changed, 194 insertions(+), 3 deletions(-) create mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java create mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java create mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java index cbbb4a0ad..075a0f103 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java @@ -26,7 +26,7 @@ public List deserialize(byte[] source) throws DeserializationExce try { return ProtoLogsDataMapper.getInstance() .fromProto(ExportLogsServiceRequest.ADAPTER.decode(source)); - } catch (IOException e) { + } catch (IOException | IllegalStateException e) { throw new DeserializationException(e); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java index d6410d4e7..463c07b75 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java @@ -26,7 +26,7 @@ public List deserialize(byte[] source) throws DeserializationExcepti try { return ProtoMetricsDataMapper.getInstance() .fromProto(ExportMetricsServiceRequest.ADAPTER.decode(source)); - } catch (IOException e) { + } catch (IOException | IllegalStateException e) { throw new DeserializationException(e); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java index eb4406ff3..b703c3ade 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java @@ -26,7 +26,7 @@ public List deserialize(byte[] source) throws DeserializationException try { return ProtoSpansDataMapper.getInstance() .fromProto(ExportTraceServiceRequest.ADAPTER.decode(source)); - } catch (IOException e) { + } catch (IOException | IllegalStateException e) { throw new DeserializationException(e); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java new file mode 100644 index 000000000..e508a34c9 --- /dev/null +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.models.LogRecordDataImpl; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.testutils.BaseSignalSerializerTest; +import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import org.junit.jupiter.api.Test; + +class LogRecordDataDeserializerTest extends BaseSignalSerializerTest { + private static final LogRecordData LOG_RECORD = + LogRecordDataImpl.builder() + .setResource(TestData.RESOURCE_FULL) + .setSpanContext(TestData.SPAN_CONTEXT) + .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) + .setAttributes(TestData.ATTRIBUTES) + .setBodyValue(Value.of("Log body")) + .setSeverity(Severity.DEBUG) + .setSeverityText("Log severity text") + .setTimestampEpochNanos(100L) + .setObservedTimestampEpochNanos(200L) + .setTotalAttributeCount(3) + .setEventName("event") + .build(); + + @Test + void verifyDeserialization() { + assertSerializeDeserialize(LOG_RECORD, LOG_RECORD); + } + + @Test + void whenDecodingMalformedMessage_wrapIntoDeserializationException() { + assertThrows( + DeserializationException.class, + () -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())); + } + + @Test + void whenDecodingTooShortMessage_wrapIntoDeserializationException() { + assertThrows( + DeserializationException.class, + () -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())); + } + + @Override + protected SignalSerializer getSerializer() { + return SignalSerializer.ofLogs(); + } + + @Override + protected SignalDeserializer getDeserializer() { + return SignalDeserializer.ofLogs(); + } +} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java new file mode 100644 index 000000000..53751e678 --- /dev/null +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.testutils.BaseSignalSerializerTest; +import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.sdk.metrics.data.MetricData; +import org.junit.jupiter.api.Test; + +class MetricDataDeserializerTest extends BaseSignalSerializerTest { + + @Test + void whenDecodingMalformedMessage_wrapIntoDeserializationException() { + assertThrows( + DeserializationException.class, + () -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())); + } + + @Test + void whenDecodingTooShortMessage_wrapIntoDeserializationException() { + assertThrows( + DeserializationException.class, + () -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())); + } + + @Override + protected SignalSerializer getSerializer() { + return SignalSerializer.ofMetrics(); + } + + @Override + protected SignalDeserializer getDeserializer() { + return SignalDeserializer.ofMetrics(); + } +} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java new file mode 100644 index 000000000..d710ca16e --- /dev/null +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java @@ -0,0 +1,68 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.testutils.BaseSignalSerializerTest; +import io.opentelemetry.contrib.disk.buffering.testutils.TestData; +import io.opentelemetry.sdk.trace.data.SpanData; +import io.opentelemetry.sdk.trace.data.StatusData; +import java.util.Collections; +import org.junit.jupiter.api.Test; + +class SpanDataDeserializerTest extends BaseSignalSerializerTest { + private static final SpanData SPAN_DATA = + SpanDataImpl.builder() + .setSpanContext(TestData.SPAN_CONTEXT) + .setParentSpanContext(TestData.PARENT_SPAN_CONTEXT) + .setName("Test span") + .setKind(SpanKind.SERVER) + .setStartEpochNanos(100L) + .setEndEpochNanos(200L) + .setStatus(StatusData.ok()) + .setAttributes(TestData.ATTRIBUTES) + .setResource(TestData.RESOURCE_FULL) + .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) + .setTotalRecordedLinks(0) + .setTotalRecordedEvents(0) + .setTotalAttributeCount(0) + .setEvents(Collections.emptyList()) + .setLinks(Collections.emptyList()) + .build(); + + @Test + void verifyDeserialization() { + assertSerializeDeserialize(SPAN_DATA, SPAN_DATA); + } + + @Test + void whenDecodingMalformedMessage_wrapIntoDeserializationException() { + assertThrows( + DeserializationException.class, + () -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())); + } + + @Test + void whenDecodingTooShortMessage_wrapIntoDeserializationException() { + assertThrows( + DeserializationException.class, + () -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())); + } + + @Override + protected SignalSerializer getSerializer() { + return SignalSerializer.ofSpans(); + } + + @Override + protected SignalDeserializer getDeserializer() { + return SignalDeserializer.ofSpans(); + } +} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java index 15c32c421..ec69e8024 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java @@ -131,5 +131,24 @@ private static LongExemplarData makeLongExemplarData(TraceFlags flags) { return ImmutableLongExemplarData.create(ATTRIBUTES, 100L, context, 1L); } + @NotNull + public static byte[] makeTooShortSignalBinary() { + return new byte[] { + (byte) 0x0A, // type + (byte) 0xFF, // defining length 255, but message is shorter + (byte) 0x01 // content + }; + } + + @NotNull + public static byte[] makeMalformedSignalBinary() { + return new byte[] { + (byte) 0x0A, // type + (byte) 0x02, // length + (byte) 0x08, // field 1, wire type 0 (varint) - this should be a nested message but isn't + (byte) 0x01 // content + }; + } + private TestData() {} } From 85c443f17b48e20da24201d1a7fda30dd2148905 Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Fri, 22 Aug 2025 16:02:00 +0000 Subject: [PATCH 211/371] Update version to 1.50.0-SNAPSHOT (#2161) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 2 ++ version.gradle.kts | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa0d0833e..410e248eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## Version 1.49.0 (2025-08-22) + ### Consistent sampling - Add updateable threshold sampler for dynamic sampling configuration diff --git a/version.gradle.kts b/version.gradle.kts index e4f74f0e1..9ea6b805e 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.49.0-SNAPSHOT" -val alphaVersion = "1.49.0-alpha-SNAPSHOT" +val stableVersion = "1.50.0-SNAPSHOT" +val alphaVersion = "1.50.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From 97a79e09fd27643acd3f68c9d7cd30780869eeda Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 22 Aug 2025 10:58:59 -0700 Subject: [PATCH 212/371] Another release workflow fix (#2164) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b7f9af7a..67a41bdae 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -182,7 +182,7 @@ jobs: cp jmx-metrics/build/libs/opentelemetry-jmx-metrics-$VERSION-alpha.jar opentelemetry-jmx-metrics.jar cp jmx-metrics/build/libs/opentelemetry-jmx-metrics-$VERSION-alpha.jar.asc opentelemetry-jmx-metrics.jar.asc cp jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar opentelemetry-jmx-scraper.jar - cp jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar opentelemetry-jmx-scraper.jar.asc + cp jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar.asc opentelemetry-jmx-scraper.jar.asc gh release create --target $GITHUB_REF_NAME \ --title "Version $VERSION" \ From e9065b3cf7a97d2269f15e4deeb8fa7fa615955c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 22 Aug 2025 10:59:19 -0700 Subject: [PATCH 213/371] Fix release workflow (#2163) --- .github/workflows/release.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 67a41bdae..f24200b61 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -189,10 +189,9 @@ jobs: --notes-file /tmp/release-notes.txt \ v$VERSION \ opentelemetry-jmx-metrics.jar \ - opentelemetry-jmx-metrics.jar.asc \ + opentelemetry-jmx-metrics.jar.asc \ opentelemetry-jmx-scraper.jar \ - opentelemetry-jmx-scraper.jar.asc - + opentelemetry-jmx-scraper.jar.asc echo "version=$VERSION" >> $GITHUB_OUTPUT From e77eb03219dc7d8e7c01f5cb2a44149df2d00b47 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 22 Aug 2025 11:44:31 -0700 Subject: [PATCH 214/371] Add already-published option to release workflow (#2165) --- .github/workflows/release.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f24200b61..5b4bdf9e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,6 +1,11 @@ name: Release on: workflow_dispatch: + inputs: + already-published: + description: 'Skip publishing, download artifacts from Maven Central instead' + default: false + type: boolean permissions: contents: read @@ -125,7 +130,9 @@ jobs: - name: Set up gradle uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + - name: Build and publish artifacts + if: ${{ !inputs.already-published }} run: ./gradlew assemble publishToSonatype closeAndReleaseSonatypeStagingRepository env: SONATYPE_USER: ${{ secrets.SONATYPE_USER }} @@ -133,6 +140,21 @@ jobs: GPG_PRIVATE_KEY: ${{ secrets.GPG_PRIVATE_KEY }} GPG_PASSWORD: ${{ secrets.GPG_PASSWORD }} + - name: Download artifacts from Maven Central (when already published) + if: ${{ inputs.already-published }} + run: | + mkdir -p jmx-metrics/build/libs + mkdir -p jmx-scraper/build/libs + + curl -L -o jmx-metrics/build/libs/opentelemetry-jmx-metrics-$VERSION-alpha.jar \ + "https://repo1.maven.org/maven2/io/opentelemetry/contrib/opentelemetry-jmx-metrics/$VERSION-alpha/opentelemetry-jmx-metrics-$VERSION-alpha.jar" + curl -L -o jmx-metrics/build/libs/opentelemetry-jmx-metrics-$VERSION-alpha.jar.asc \ + "https://repo1.maven.org/maven2/io/opentelemetry/contrib/opentelemetry-jmx-metrics/$VERSION-alpha/opentelemetry-jmx-metrics-$VERSION-alpha.jar.asc" + curl -L -o jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar \ + "https://repo1.maven.org/maven2/io/opentelemetry/contrib/opentelemetry-jmx-scraper/$VERSION-alpha/opentelemetry-jmx-scraper-$VERSION-alpha.jar" + curl -L -o jmx-scraper/build/libs/opentelemetry-jmx-scraper-$VERSION-alpha.jar.asc \ + "https://repo1.maven.org/maven2/io/opentelemetry/contrib/opentelemetry-jmx-scraper/$VERSION-alpha/opentelemetry-jmx-scraper-$VERSION-alpha.jar.asc" + - name: Generate release notes env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 221447c8c2460188a2c304448ab811a8a059fdf6 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 09:03:35 -0700 Subject: [PATCH 215/371] chore(deps): update actions/setup-java action to v5 (#2180) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/build-common.yml | 8 ++++---- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index f6b873119..92f753c19 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -19,7 +19,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index e66619ec9..75490653b 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 @@ -71,14 +71,14 @@ jobs: - id: setup-java-test name: Set up Java ${{ matrix.test-java-version }} for tests - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: ${{ matrix.test-java-version }} - id: setup-java name: Set up Java for build - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 @@ -103,7 +103,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 2620367bb..b93eceaea 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 63d475c02..0ff8fbe7a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -40,7 +40,7 @@ jobs: - name: Set up Java 17 if: matrix.language == 'java' - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index 724b7605b..63522b47c 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -18,7 +18,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5b4bdf9e5..7d9f1827f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 @@ -40,7 +40,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 @@ -123,7 +123,7 @@ jobs: fetch-depth: 0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 From 18d4f1cdbb249f155646288389541d0baeb0a96c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 16:04:54 +0000 Subject: [PATCH 216/371] fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.38.0 (#2178) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index f81e5e521..0f8b121d9 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -24,7 +24,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.37.1") + implementation("com.google.auth:google-auth-library-oauth2-http:1.38.0") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From ff4d52c74bb092d1cf722ff3c08cf34bcbaa81ec Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 10:22:04 -0700 Subject: [PATCH 217/371] fix(deps): update dependency net.ltgt.gradle:gradle-nullaway-plugin to v2.3.0 (#2179) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 6de6f8e1e..87a4312f4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -14,7 +14,7 @@ dependencies { // When updating, update above in plugins too implementation("com.diffplug.spotless:spotless-plugin-gradle:7.2.1") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") - implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.2.0") + implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.3.0") implementation("org.owasp:dependency-check-gradle:12.1.3") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") implementation("com.gradle:develocity-gradle-plugin:4.1.1") From 1b1a8d24774f7287e400de8e1bded21c5648d9f3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 25 Aug 2025 14:54:39 -0700 Subject: [PATCH 218/371] Better backport error message (#2166) --- .github/workflows/backport.yml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index eeb3ce5e3..582a82f3f 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -12,7 +12,7 @@ permissions: jobs: backport: permissions: - contents: write # for Git to git push + contents: write # for git push to PR branch runs-on: ubuntu-latest steps: - run: | @@ -48,6 +48,15 @@ jobs: git checkout -b $branch git cherry-pick $commit + + if git diff --name-only HEAD~1 HEAD | grep -q '^\.github/workflows/'; then + echo "::error::This PR contains changes to workflow files (.github/workflows/)." + echo "::error::Workflow files cannot be automatically backported because the standard" + echo "::error::GitHub token doesn't have the required 'workflow' write permission." + echo "::error::Please backport this PR manually." + exit 1 + fi + git push --set-upstream origin $branch gh pr create --title "[$GITHUB_REF_NAME] $title" \ --body "Clean cherry-pick of #$NUMBER to the \`$GITHUB_REF_NAME\` branch." \ From 3e2f39ee1cdc9c5877d3a640349295ca8224bbd5 Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Mon, 25 Aug 2025 15:53:46 -0700 Subject: [PATCH 219/371] Merge change log updates from release/v1.49.x (#2184) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 410e248eb..07f71af44 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Unreleased -## Version 1.49.0 (2025-08-22) +## Version 1.49.0 (2025-08-25) ### Consistent sampling From 7a0d8faf1ecdfdce98cb36039e6ba368af3bd3f7 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 03:45:29 -0700 Subject: [PATCH 220/371] Fix link check workflow (#2177) --- .github/workflows/reusable-link-check.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index da70758ff..a8b6b6e35 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -26,7 +26,8 @@ jobs: run: | merge_base=$(git merge-base origin/${{ github.base_ref }} HEAD) # Using lychee's default extension filter here to match when it runs against all files - modified_files=$(git diff --name-only $merge_base...${{ github.event.pull_request.head.sha }} \ + # Note: --diff-filter=d filters out deleted files + modified_files=$(git diff --name-only --diff-filter=d $merge_base...${{ github.event.pull_request.head.sha }} \ | grep -E '\.(md|mkd|mdx|mdown|mdwn|mkdn|mkdown|markdown|html|htm|txt)$' \ | tr '\n' ' ' || true) echo "files=$modified_files" >> $GITHUB_OUTPUT From 740a1b5d8a73abd96394ad9689125ae9880ab6aa Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 07:20:36 -0700 Subject: [PATCH 221/371] Remove the 'all' module (#2175) --- all/README.md | 4 --- all/build.gradle.kts | 64 -------------------------------------------- settings.gradle.kts | 1 - 3 files changed, 69 deletions(-) delete mode 100644 all/README.md delete mode 100644 all/build.gradle.kts diff --git a/all/README.md b/all/README.md deleted file mode 100644 index dca83da7b..000000000 --- a/all/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# opentelemetry-contrib-all (utility project) - -This is a utility project which depends on all other projects in this repository. -We use it for collecting all coverage reports from all modules for uploading to codecov. diff --git a/all/build.gradle.kts b/all/build.gradle.kts deleted file mode 100644 index 58f0f6d72..000000000 --- a/all/build.gradle.kts +++ /dev/null @@ -1,64 +0,0 @@ -plugins { - `jacoco-report-aggregation` - - id("otel.java-conventions") -} - -description = "OpenTelemetry Contrib All" - -dependencies { - rootProject.subprojects.forEach { subproject -> - // Generate aggregate coverage report for published modules that enable jacoco. - subproject.plugins.withId("jacoco") { - subproject.plugins.withId("maven-publish") { - // TODO(anuraaga): Figure out how to avoid transitive dependencies being pulled into jacoco due to the use - // of shadow plugin. - if (subproject.name != "jmx-metrics") { - implementation(project(subproject.path)) { - isTransitive = false - } - } - } - } - } -} - -tasks { - // We don't compile anything here. This project is mostly for - // aggregating jacoco reports and it doesn't work if this isn't at least as high as the - // highest supported Java version in any of our projects. Most of our projects target - // Java 8, but some target Java 11 or 17. - withType(JavaCompile::class) { - options.release.set(17) - } -} - -afterEvaluate { - tasks { - testCodeCoverageReport { - classDirectories.setFrom( - classDirectories.files.map { - zipTree(it).filter { - // Exclude mrjar (jacoco complains), shaded, and generated code - !it.absolutePath.contains("META-INF/versions/") && - !it.absolutePath.contains("AutoValue_") - } - }, - ) - - reports { - // xml is usually used to integrate code coverage with - // other tools like SonarQube, Coveralls or Codecov - xml.required.set(true) - - // HTML reports can be used to see code coverage - // without any external tools - html.required.set(true) - } - } - } -} - -dependencyCheck { - skip = true -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 5502252e7..a33865fa5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -34,7 +34,6 @@ develocity { rootProject.name = "opentelemetry-java-contrib" -include(":all") include(":aws-resources") include(":aws-xray") include(":aws-xray-propagator") From b52a886bb223b50d5c97c3ddce9f7a0994cb491b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 07:21:14 -0700 Subject: [PATCH 222/371] Remove the examples module (#2176) --- example/README.md | 3 --- example/build.gradle.kts | 13 ------------- .../contrib/example/Library.java | 18 ------------------ .../contrib/example/LibraryTest.java | 19 ------------------- settings.gradle.kts | 1 - 5 files changed, 54 deletions(-) delete mode 100644 example/README.md delete mode 100644 example/build.gradle.kts delete mode 100644 example/src/main/java/io/opentelemetry/contrib/example/Library.java delete mode 100644 example/src/test/java/io/opentelemetry/contrib/example/LibraryTest.java diff --git a/example/README.md b/example/README.md deleted file mode 100644 index 43333d2c2..000000000 --- a/example/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# Example Library - -This is an example library intended to be used as a template for easy additions to the OpenTelemetry Java Contrib project. diff --git a/example/build.gradle.kts b/example/build.gradle.kts deleted file mode 100644 index 898191643..000000000 --- a/example/build.gradle.kts +++ /dev/null @@ -1,13 +0,0 @@ -plugins { - id("otel.java-conventions") -} - -description = "An example OpenTelemetry Java Contrib library" - -tasks { - jar { - manifest { - attributes["Main-Class"] = "io.opentelemetry.contrib.example.Library" - } - } -} diff --git a/example/src/main/java/io/opentelemetry/contrib/example/Library.java b/example/src/main/java/io/opentelemetry/contrib/example/Library.java deleted file mode 100644 index 289f72ea8..000000000 --- a/example/src/main/java/io/opentelemetry/contrib/example/Library.java +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.example; - -public class Library { - - public boolean myMethod() { - return true; - } - - @SuppressWarnings("SystemOut") - public static void main(String... args) { - System.out.println("ExampleLibrary.main"); - } -} diff --git a/example/src/test/java/io/opentelemetry/contrib/example/LibraryTest.java b/example/src/test/java/io/opentelemetry/contrib/example/LibraryTest.java deleted file mode 100644 index a9c897576..000000000 --- a/example/src/test/java/io/opentelemetry/contrib/example/LibraryTest.java +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.example; - -import static org.assertj.core.api.Assertions.assertThat; - -import org.junit.jupiter.api.Test; - -class LibraryTest { - - @Test - void myMethod() { - Library library = new Library(); - assertThat(library.myMethod()).isTrue(); - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index a33865fa5..0ea557d7d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -44,7 +44,6 @@ include(":cloudfoundry-resources") include(":consistent-sampling") include(":dependencyManagement") include(":disk-buffering") -include(":example") include(":ibm-mq-metrics") include(":jfr-events") include(":jfr-connection") From c2506ed990fadf3d8b85836f9d62c3b6090db699 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 07:43:54 -0700 Subject: [PATCH 223/371] Add copilot coding agent setup steps (#2171) --- .github/workflows/copilot-setup-steps.yml | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/copilot-setup-steps.yml diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 000000000..bb9b2badb --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,34 @@ +# Custom setup steps for GitHub Copilot coding agent to speed up Copilot's work on coding tasks +name: "Copilot Setup Steps" + +on: + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + push: + paths: + - .github/workflows/copilot-setup-steps.yml + workflow_dispatch: + +permissions: + contents: read + +jobs: + copilot-setup-steps: # Job name required by GitHub Copilot coding agent + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up JDK for running Gradle + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: 17 + + - name: Set up gradle + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + + - name: Build project and download dependencies + run: ./gradlew build -x test From d1603cf604811834fec97cd6a023f604fbaaf35d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 08:04:17 -0700 Subject: [PATCH 224/371] Fix link check failures (#2173) --- .github/scripts/lychee-config.toml | 6 +----- .github/workflows/reusable-link-check.yml | 15 ++++++++++++++- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/scripts/lychee-config.toml b/.github/scripts/lychee-config.toml index d368a2404..b2d5285f1 100644 --- a/.github/scripts/lychee-config.toml +++ b/.github/scripts/lychee-config.toml @@ -6,11 +6,7 @@ max_concurrency = 4 # Check link anchors include_fragments = true -# excluding links to pull requests and issues is done for performance -# sonatype snapshots are currrently unbrowseable exclude = [ + # excluding links to pull requests and issues is done for performance "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$", - '^https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/contrib/$', ] - - diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index a8b6b6e35..8dccc28ca 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -33,6 +33,19 @@ jobs: echo "files=$modified_files" >> $GITHUB_OUTPUT echo "Modified files: $modified_files" + - name: Check if lychee config was modified + if: github.event_name == 'pull_request' + id: config-check + run: | + merge_base=$(git merge-base origin/${{ github.base_ref }} HEAD) + config_modified=$(git diff --name-only $merge_base...${{ github.event.pull_request.head.sha }} \ + | grep -E '\.github/scripts/(lychee-config\.toml|link-check\.sh|dependencies\.Dockerfile)$' || true) + if [ -n "$config_modified" ]; then + echo "modified=true" >> $GITHUB_OUTPUT + else + echo "modified=false" >> $GITHUB_OUTPUT + fi + - name: Link check - all links (modified files only) if: github.event_name == 'pull_request' && steps.modified-files.outputs.files != '' env: @@ -40,7 +53,7 @@ jobs: run: ./.github/scripts/link-check.sh ${{ steps.modified-files.outputs.files }} - name: Link check - all links (all files) - if: github.event_name != 'pull_request' + if: github.event_name != 'pull_request' || steps.config-check.outputs.modified == 'true' env: GITHUB_TOKEN: ${{ github.token }} run: ./.github/scripts/link-check.sh From 8ef63a65037dcd2569a835fdac107b670eb7fd96 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 08:29:56 -0700 Subject: [PATCH 225/371] Add copilot review instructions (#2170) --- .github/copilot-instructions.md | 39 +++++++ CONTRIBUTING.md | 89 +++++++--------- README.md | 23 +--- docs/style-guide.md | 180 ++++++++++++++++++++++++++++++++ 4 files changed, 256 insertions(+), 75 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 docs/style-guide.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 000000000..9b11e2264 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,39 @@ +# Copilot Instructions for OpenTelemetry Java Contrib + +This repository provides observability instrumentation for Java applications. + +## Code Review Priorities + +### Style Guide Compliance + +**PRIORITY**: Verify that all code changes follow the [Style Guide](../docs/style-guide.md). Check: + +- Code formatting (auto-formatting, static imports, class organization) +- Java language conventions (`final` usage, `@Nullable` annotations, `Optional` usage) +- Performance constraints (hot path allocations) +- Implementation patterns (SPI registration, configuration conventions) +- Gradle conventions (Kotlin DSL, plugin usage, module naming) +- Documentation standards (README files, deprecation processes) + +### Critical Areas + +- **Public APIs**: Changes affect downstream users and require careful review +- **Performance**: Instrumentation must have minimal overhead +- **Thread Safety**: Ensure safe concurrent access patterns +- **Memory Management**: Prevent leaks and excessive allocations + +### Quality Standards + +- Proper error handling with appropriate logging levels +- OpenTelemetry specification and semantic convention compliance +- Resource cleanup and lifecycle management +- Comprehensive unit tests for new functionality + +## Coding Agent Instructions + +When implementing changes or new features: + +1. Follow all [Style Guide](../docs/style-guide.md) conventions and the Code Review Priorities above +2. Run tests to ensure they still pass (use `./gradlew test` and `./gradlew integrationTest` as needed) +3. **Always run `./gradlew spotlessApply`** after making code changes to ensure proper formatting +4. Run markdown lint to ensure it still passes: `npx markdownlint-cli@0.45.0 -c .github/config/markdownlint.yml **/*.md` diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6d8a6a806..04a50c300 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,82 +1,65 @@ # Contributing -Welcome to the OpenTelemetry Java Contrib Repository! +Welcome to the OpenTelemetry Java Contrib repository! ## Introduction -This repository focuses on providing tools and utilities for Java-based observability, such as remote JMX metric gathering and reporting. We’re excited to have you here! Whether you’re fixing a bug, adding a feature, or suggesting an idea, your contributions are invaluable. +This repository provides observability libraries and utilities for Java applications that complement +the [OpenTelemetry Java SDK](https://github.com/open-telemetry/opentelemetry-java) and +[OpenTelemetry Java Instrumentation](https://github.com/open-telemetry/opentelemetry-java-instrumentation) +projects. -Before submitting new features or changes to current functionality, it is recommended to first -[open an issue](https://github.com/open-telemetry/opentelemetry-java-contrib/issues/new) -and discuss your ideas or propose the changes you wish to make. - -Questions? Ask in the OpenTelemetry [java channel](https://cloud-native.slack.com/archives/C014L2KCTE3) +Before submitting new features or changes, please consider +[opening an issue](https://github.com/open-telemetry/opentelemetry-java-contrib/issues/new) first to +discuss your ideas. Pull requests for bug fixes are always welcome! -## Pre-requisites - -To work with this repository, ensure you have: - -### Tools: - -Java 17 or higher - -### Platform Notes: +## Building and Testing -macOS/Linux: Ensure JAVA_HOME is set correctly. +While most modules target Java 8, building this project requires Java 17 or higher. -## Workflow - -1. Fork the repository -2. Clone locally -3. Create a branch before working on an issue - -## Local Run/Build - -In order to build and test this whole repository you need JDK 11+. - -#### Snapshot builds - -For developers testing code changes before a release is complete, there are -snapshot builds of the `main` branch. They are available from -the Sonatype snapshot repository at `https://central.sonatype.com/repository/maven-snapshots/` -([browse](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/contrib/)). - -#### Building from source - -Building using Java 11+: +To build the project: ```bash -$ java -version +./gradlew assemble ``` +To run the tests: + ```bash -$ ./gradlew assemble +./gradlew test ``` -## Testing +Some modules include integration tests that can be run with: ```bash -$ ./gradlew test +./gradlew integrationTest ``` -### Some modules have integration tests +## Snapshot Builds -``` -$ ./gradlew integrationTest -``` +Snapshot builds of the `main` branch are available from the Sonatype snapshot repository at: +`https://central.sonatype.com/repository/maven-snapshots/` +([browse](https://central.sonatype.com/service/rest/repository/browse/maven-snapshots/io/opentelemetry/contrib/)). + +## Style Guide + +See [Style Guide](docs/style-guide.md). -Follow the Java Instrumentation [Style Guide](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/docs/contributing/style-guideline.md) from the opentelemetry-java-instrumentation repository. +## Pull Request Guidelines -Failure? Check logs for errors or mismatched dependencies. +When submitting a pull request, please ensure that you: -## Gradle conventions +- Clearly describe the change and its motivation +- Mention any breaking changes +- Include tests for new functionality +- Follow the [Style Guide](docs/style-guide.md) -- Use kotlin instead of groovy -- Plugin versions should be specified in `settings.gradle.kts`, not in individual modules -- All modules use `plugins { id("otel.java-conventions") }` +## Getting Help -## Further Help +If you need assistance or have questions: -Join [#otel-java](https://cloud-native.slack.com/archives/C014L2KCTE3) on OpenTelemetry Slack +- Post on the [#otel-java](https://cloud-native.slack.com/archives/C014L2KCTE3) Slack channel +- [Open an issue](https://github.com/open-telemetry/opentelemetry-java-contrib/issues/new/choose) in + this repository diff --git a/README.md b/README.md index c42ea7e27..29b2e01f2 100644 --- a/README.md +++ b/README.md @@ -48,30 +48,9 @@ On reaching stable status, the `otel.stable` value in `gradle.properties` should Note that currently all the libraries are released together with the version of this repo, so breaking changes (after stable status is reached) would bump the major version of all libraries together. This could get complicated so `stable` has a high bar. -## Getting Started - -```bash -# Apply formatting -$ ./gradlew spotlessApply - -# Build the complete project -$ ./gradlew build - -# Run integration tests -$ ./gradlew integrationTest - -# Clean artifacts -$ ./gradlew clean -``` - ## Contributing -The Java Contrib project was initially formed to provide methods of easy remote JMX metric gathering and reporting, -which is actively in development. If you have an idea for a similar use case in the metrics, traces, or logging -domain we would be very interested in supporting it. Please -[open an issue](https://github.com/open-telemetry/opentelemetry-java-contrib/issues/new/choose) to share your idea or -suggestion. PRs are always welcome and greatly appreciated, but for larger functional changes a pre-coding introduction -can be helpful to ensure this is the correct place and that active or conflicting efforts don't exist. +See [CONTRIBUTING.md](CONTRIBUTING.md). ### Maintainers diff --git a/docs/style-guide.md b/docs/style-guide.md new file mode 100644 index 000000000..54dc63cb5 --- /dev/null +++ b/docs/style-guide.md @@ -0,0 +1,180 @@ +# Style Guide + +This project follows the +[Google Java Style Guide](https://google.github.io/styleguide/javaguide.html). + +## Code Formatting + +### Auto-formatting + +The build will fail if source code is not formatted according to Google Java Style. + +Run the following command to reformat all files: + +```bash +./gradlew spotlessApply +``` + +For IntelliJ users, an `.editorconfig` file is provided that IntelliJ will automatically use to +adjust code formatting settings. However, it does not support all required rules, so you may still +need to run `./gradlew spotlessApply` periodically. + +### Static imports + +Consider statically importing the following commonly used methods and constants: + +- **Test methods** + - `io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.*` + - `org.assertj.core.api.Assertions.*` + - `org.mockito.Mockito.*` + - `org.mockito.ArgumentMatchers.*` +- **Utility methods** + - `io.opentelemetry.api.common.AttributeKey.*` + - `java.util.Arrays` - asList, stream + - `java.util.Collections` - singleton*, empty*, unmodifiable*, synchronized*, checked* + - `java.util.Objects` - requireNonNull + - `java.util.function.Function` - identity + - `java.util.stream.Collectors.*` +- **Utility constants** + - `java.util.Locale.*` + - `java.util.concurrent.TimeUnit.*` + - `java.util.logging.Level.*` + - `java.nio.charset.StandardCharsets.*` +- **OpenTelemetry semantic convention constants** + - All constants under `io.opentelemetry.semconv.**`, except for + `io.opentelemetry.semconv.SchemaUrls.*` constants. + +### Class organization + +Prefer this order: + +- Static fields (final before non-final) +- Instance fields (final before non-final) +- Constructors +- Methods +- Nested classes + +**Method ordering**: Place calling methods above the methods they call. For example, place private +methods below the non-private methods that use them. + +**Static utility classes**: Place the private constructor (used to prevent instantiation) after all +methods. + +## Java Language Conventions + +### Visibility modifiers + +Follow the principle of minimal necessary visibility. Use the most restrictive access modifier that +still allows the code to function correctly. + +### Internal packages + +Classes in `.internal` packages are not considered public API and may change without notice. These +packages contain implementation details that should not be used by external consumers. + +- Use `.internal` packages for implementation classes that need to be public within the module but + should not be used externally +- Try to avoid referencing `.internal` classes from other modules + +### `final` keyword usage + +Public non-internal classes should be declared `final` where possible. Internal and non-public +classes should not be declared `final`. + +Methods should only be declared `final` if they are in public non-internal non-final classes. + +Fields should be declared `final` where possible. + +Method parameters and local variables should never be declared `final`. + +### `@Nullable` annotation usage + +**Note: This section is aspirational and may not reflect the current codebase.** + +Annotate all parameters and fields that can be `null` with `@Nullable` (specifically +`javax.annotation.Nullable`, which is included by the `otel.java-conventions` Gradle plugin as a +`compileOnly` dependency). + +`@NonNull` is unnecessary as it is the default. + +**Defensive programming**: Public APIs should still check for `null` parameters even if not +annotated with `@Nullable`. Internal APIs do not need these checks. + +### `Optional` usage + +Following the reasoning from +[Writing a Java library with better experience (slide 12)](https://speakerdeck.com/trustin/writing-a-java-library-with-better-experience?slide=12), +`java.util.Optional` usage is kept to a minimum. + +**Guidelines**: + +- `Optional` shouldn't appear in public API signatures +- Avoid `Optional` on the hot path (instrumentation code), unless the instrumented library uses it + +## Tooling conventions + +### AssertJ + +Prefer AssertJ assertions over JUnit assertions (assertEquals, assertTrue, etc.) for better error +messages. + +### JUnit + +Test classes and test methods should generally be package-protected (no explicit visibility +modifier) rather than `public`. This follows the principle of minimal necessary visibility and is +sufficient for JUnit to discover and execute tests. + +### AutoService + +Use the `@AutoService` annotation when implementing SPI interfaces. This automatically generates the +necessary `META-INF/services/` files at compile time, eliminating the need to manually create and +maintain service registration files. + +```java +@AutoService(AutoConfigurationCustomizerProvider.class) +public class MyCustomizerProvider implements AutoConfigurationCustomizerProvider { + // implementation +} +``` + +### Gradle + +- Use Kotlin instead of Groovy for build scripts +- Plugin versions should be specified in `settings.gradle.kts`, not in individual modules +- All modules should use `plugins { id("otel.java-conventions") }` +- Set module names with `otelJava.moduleName.set("io.opentelemetry.contrib.mymodule")` + +## Configuration + +- Use `otel.` prefix for all configuration property keys +- Read configuration via the `ConfigProperties` interface +- Provide sensible defaults and document all options +- Validate configuration early with clear error messages + +## Performance + +Avoid allocations on the hot path (instrumentation code) whenever possible. This includes `Iterator` +allocations from collections; note that `for (SomeType t : plainJavaArray)` does not allocate an +iterator object. + +Non-allocating Stream API usage on the hot path is acceptable but may not fit the surrounding code +style; this is a judgment call. Some Stream APIs make efficient allocation difficult (e.g., +`collect` with pre-sized sink data structures involves convoluted `Supplier` code, or lambdas passed +to `forEach` may be capturing/allocating lambdas). + +## Documentation + +### Component README files + +- Include a component owners section in each module's README +- Document configuration options with examples + +### Deprecation and breaking changes + +Breaking changes are allowed in unstable modules (published with `-alpha` version suffix). + +1. Mark APIs with `@Deprecated` and a removal timeline (there must be at least one release with the + API marked as deprecated before removing it) +2. Document the replacement in Javadoc with `@deprecated` tag +3. Note the migration path for breaking changes under a "Migration notes" section of CHANGELOG.md + (create this section at the top of the Unreleased section if not already present) From 229264caad66e546fc8a89d3cb2b236a6c76e03b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 26 Aug 2025 11:42:49 -0700 Subject: [PATCH 226/371] Convert JUnit assertions to AssertJ assertions (#2172) --- .../storage/files/utils/FileStreamTest.java | 2 +- .../gcp/resource/GCPResourceProviderTest.java | 2 +- ...corderDiagnosticCommandConnectionTest.java | 36 ++--- .../jfr/connection/OpenDataUtilsTest.java | 4 +- .../RecordingConfigurationTest.java | 23 +-- .../jfr/connection/RecordingOptionsTest.java | 55 ++++---- .../contrib/jfr/connection/RecordingTest.java | 40 +++--- .../kafka/KafkaSpanExporterBuilderTest.java | 133 +++++++++--------- .../kafka/SpanDataDeserializerTest.java | 10 +- .../contrib/kafka/SpanDataSerializerTest.java | 19 +-- .../FilteringLogRecordProcessorTest.java | 8 +- .../InterceptableLogRecordExporterTest.java | 20 +-- .../InterceptableMetricExporterTest.java | 7 +- .../InterceptableSpanExporterTest.java | 18 +-- .../JettyServiceNameDetectorTest.java | 21 ++- 15 files changed, 194 insertions(+), 204 deletions(-) diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java index 1d801db7b..c2ad06f28 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/utils/FileStreamTest.java @@ -61,7 +61,7 @@ void truncateTop() throws IOException { // Truncate all available data stream.truncateTop(3); - assertThat(stream.size()).isEqualTo(0); + assertThat(stream).isEmpty(); assertThat(readString(temporaryFile)).isEqualTo(""); stream.close(); diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java index 9b17b22d2..b0683994e 100644 --- a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java @@ -52,7 +52,7 @@ import static io.opentelemetry.semconv.incubating.HostIncubatingAttributes.HOST_NAME; import static io.opentelemetry.semconv.incubating.HostIncubatingAttributes.HOST_TYPE; import static io.opentelemetry.semconv.incubating.K8sIncubatingAttributes.K8S_CLUSTER_NAME; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.verify; import com.google.cloud.opentelemetry.detection.DetectedPlatform; diff --git a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/FlightRecorderDiagnosticCommandConnectionTest.java b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/FlightRecorderDiagnosticCommandConnectionTest.java index 8cf7f06e1..280c06909 100644 --- a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/FlightRecorderDiagnosticCommandConnectionTest.java +++ b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/FlightRecorderDiagnosticCommandConnectionTest.java @@ -5,9 +5,9 @@ package io.opentelemetry.contrib.jfr.connection; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.mock; @@ -33,32 +33,32 @@ void assertCommercialFeaturesUnlocked() throws Exception { @Test void assertCommercialFeaturesLockedThrows() throws Exception { - assertThrows( - JfrConnectionException.class, - () -> { - ObjectName objectName = mock(ObjectName.class); - MBeanServerConnection mBeanServerConnection = mockMbeanServer(objectName, "locked"); - FlightRecorderDiagnosticCommandConnection.assertCommercialFeaturesUnlocked( - mBeanServerConnection, objectName); - }); + assertThatThrownBy( + () -> { + ObjectName objectName = mock(ObjectName.class); + MBeanServerConnection mBeanServerConnection = mockMbeanServer(objectName, "locked"); + FlightRecorderDiagnosticCommandConnection.assertCommercialFeaturesUnlocked( + mBeanServerConnection, objectName); + }) + .isInstanceOf(JfrConnectionException.class); } @Test void closeRecording() throws Exception { - assertThrows(UnsupportedOperationException.class, () -> createconnection().closeRecording(1)); + assertThatThrownBy(() -> createconnection().closeRecording(1)) + .isInstanceOf(UnsupportedOperationException.class); } @Test void testGetStream() throws Exception { - assertThrows( - UnsupportedOperationException.class, - () -> createconnection().getStream(1L, null, null, 0L)); + assertThatThrownBy(() -> createconnection().getStream(1L, null, null, 0L)) + .isInstanceOf(UnsupportedOperationException.class); } @Test void testCloneRecording() throws Exception { - assertThrows( - UnsupportedOperationException.class, () -> createconnection().cloneRecording(1, false)); + assertThatThrownBy(() -> createconnection().cloneRecording(1, false)) + .isInstanceOf(UnsupportedOperationException.class); } @Test @@ -73,7 +73,7 @@ void startRecordingParsesIdCorrectly() throws Exception { long id = connection.startRecording( new RecordingOptions.Builder().build(), RecordingConfiguration.PROFILE_CONFIGURATION); - assertEquals(id, 99); + assertThat(id).isEqualTo(99); } @Test diff --git a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/OpenDataUtilsTest.java b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/OpenDataUtilsTest.java index 9059980c3..041232b54 100644 --- a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/OpenDataUtilsTest.java +++ b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/OpenDataUtilsTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.jfr.connection; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import java.lang.management.ManagementFactory; import java.util.HashMap; @@ -49,6 +49,6 @@ void makeOpenData() throws Exception { mBeanServerConnection.invoke( objectInstance.getObjectName(), "getRecordingSettings", args, argTypes); - assertEquals(expected, actual); + assertThat(actual).isEqualTo(expected); } } diff --git a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingConfigurationTest.java b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingConfigurationTest.java index d34bb3f45..091a6ca5f 100644 --- a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingConfigurationTest.java +++ b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingConfigurationTest.java @@ -5,10 +5,9 @@ package io.opentelemetry.contrib.jfr.connection; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.fail; import java.io.IOException; import java.nio.file.Files; @@ -44,18 +43,20 @@ void tearDown() { @Test void nullConfigThrows() { - assertThrows(IllegalArgumentException.class, () -> new JfcFileConfiguration(null)); + assertThatThrownBy(() -> new JfcFileConfiguration(null)) + .isInstanceOf(IllegalArgumentException.class); } @Test void brokenJfcConfigFileThrowsError() { - assertThrows(RuntimeMBeanException.class, () -> executeRecording("brokenJfcFile.jfc")); + assertThatThrownBy(() -> executeRecording("brokenJfcFile.jfc")) + .isInstanceOf(RuntimeMBeanException.class); } @Test void jfcFileFromInputStreamCanBeRead() { IItemCollection recordingContent = executeRecording("sampleJfcFile.jfc"); - assertTrue(containsEvent(recordingContent, "jdk.ThreadAllocationStatistics")); + assertThat(containsEvent(recordingContent, "jdk.ThreadAllocationStatistics")).isTrue(); } @Test @@ -68,9 +69,9 @@ void mapConfiguration() { RecordingConfiguration recordingConfiguration = new MapConfiguration(recordingConfigAsMap); IItemCollection recordingContent = excecuteRecordingWithConfig(recordingConfiguration); - assertNotNull(recordingContent, "excecuteRecordingWithConfig returned null"); - assertTrue(containsEvent(recordingContent, "jdk.ObjectAllocationInNewTLAB")); - assertTrue(containsEvent(recordingContent, "jdk.ObjectAllocationOutsideTLAB")); + assertThat(recordingContent).isNotNull(); + assertThat(containsEvent(recordingContent, "jdk.ObjectAllocationInNewTLAB")).isTrue(); + assertThat(containsEvent(recordingContent, "jdk.ObjectAllocationOutsideTLAB")).isTrue(); } private static boolean containsEvent(IItemCollection recordingContent, String eventName) { @@ -110,7 +111,7 @@ private IItemCollection excecuteRecordingWithConfig(RecordingConfiguration confi } recording.stop(); recording.dump(dumpFile.toString()); - assertTrue(Files.exists(dumpFile)); + assertThat(dumpFile).exists(); try { return JfrLoaderToolkit.loadEvents(dumpFile.toFile()); diff --git a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingOptionsTest.java b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingOptionsTest.java index 9291fb7f2..2ca0135ba 100644 --- a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingOptionsTest.java +++ b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingOptionsTest.java @@ -5,8 +5,8 @@ package io.opentelemetry.contrib.jfr.connection; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.errorprone.annotations.Keep; import java.util.HashMap; @@ -33,14 +33,14 @@ private static Stream testGetName() { @MethodSource void testGetName(String testValue, String expected) { RecordingOptions opts = new RecordingOptions.Builder().name(testValue).build(); - assertEquals(expected, opts.getName()); + assertThat(opts.getName()).isEqualTo(expected); } @Test void testGetNameDefault() { String expected = ""; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getName()); + assertThat(opts.getName()).isEqualTo(expected); } @Keep @@ -64,14 +64,14 @@ static Stream testGetMaxAge() { @MethodSource void testGetMaxAge(String testValue, String expected) { RecordingOptions opts = new RecordingOptions.Builder().maxAge(testValue).build(); - assertEquals(expected, opts.getMaxAge()); + assertThat(opts.getMaxAge()).isEqualTo(expected); } @Test void testGetMaxAgeDefault() { String expected = "0"; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getMaxAge()); + assertThat(opts.getMaxAge()).isEqualTo(expected); } @Keep @@ -91,9 +91,8 @@ private static Stream testGetMaxAgeNegative() { @ParameterizedTest @MethodSource void testGetMaxAgeNegative(String badValue) { - assertThrows( - IllegalArgumentException.class, - () -> new RecordingOptions.Builder().maxAge(badValue).build()); + assertThatThrownBy(() -> new RecordingOptions.Builder().maxAge(badValue).build()) + .isInstanceOf(IllegalArgumentException.class); } @Keep @@ -113,14 +112,14 @@ private static Stream testGetMaxSize() { @MethodSource void testGetMaxSize(String testValue, String expected) { RecordingOptions opts = new RecordingOptions.Builder().maxSize(testValue).build(); - assertEquals(expected, opts.getMaxSize()); + assertThat(opts.getMaxSize()).isEqualTo(expected); } @Test void testGetMaxSizeDefault() { String expected = "0"; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getMaxSize()); + assertThat(opts.getMaxSize()).isEqualTo(expected); } @Keep @@ -135,30 +134,29 @@ private static Stream testGetMaxSizeNegative() { @ParameterizedTest @MethodSource void testGetMaxSizeNegative(String badValue) { - assertThrows( - IllegalArgumentException.class, - () -> new RecordingOptions.Builder().maxSize(badValue).build()); + assertThatThrownBy(() -> new RecordingOptions.Builder().maxSize(badValue).build()) + .isInstanceOf(IllegalArgumentException.class); } @Test void testGetDumpOnExit() { String expected = "true"; RecordingOptions opts = new RecordingOptions.Builder().dumpOnExit(expected).build(); - assertEquals(expected, opts.getDumpOnExit()); + assertThat(opts.getDumpOnExit()).isEqualTo(expected); } @Test void testGetDumpOnExitDefault() { String expected = "false"; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getDumpOnExit()); + assertThat(opts.getDumpOnExit()).isEqualTo(expected); } @Test void testGetDumpOnExitBadValue() { String expected = "false"; RecordingOptions opts = new RecordingOptions.Builder().dumpOnExit("BAD_VALUE").build(); - assertEquals(expected, opts.getDumpOnExit()); + assertThat(opts.getDumpOnExit()).isEqualTo(expected); } @Keep @@ -175,35 +173,35 @@ private static Stream testGetDestination() { @MethodSource void testGetDestination(String testValue, String expected) { RecordingOptions opts = new RecordingOptions.Builder().destination(testValue).build(); - assertEquals(expected, opts.getDestination()); + assertThat(opts.getDestination()).isEqualTo(expected); } @Test void testGetDestinationDefault() { String expected = ""; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getDestination()); + assertThat(opts.getDestination()).isEqualTo(expected); } @Test void testGetDisk() { String expected = "true"; RecordingOptions opts = new RecordingOptions.Builder().disk(expected).build(); - assertEquals(expected, opts.getDisk()); + assertThat(opts.getDisk()).isEqualTo(expected); } @Test void testGetDiskDefault() { String expected = "false"; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getDisk()); + assertThat(opts.getDisk()).isEqualTo(expected); } @Test void testGetDiskBadValue() { String expected = "false"; RecordingOptions opts = new RecordingOptions.Builder().disk("BAD_VALUE").build(); - assertEquals(expected, opts.getDisk()); + assertThat(opts.getDisk()).isEqualTo(expected); } @Keep @@ -227,14 +225,14 @@ private static Stream testGetDuration() { @MethodSource void testGetDuration(String testValue, String expected) { RecordingOptions opts = new RecordingOptions.Builder().duration(testValue).build(); - assertEquals(expected, opts.getDuration()); + assertThat(opts.getDuration()).isEqualTo(expected); } @Test void testGetDurationDefault() { String expected = "0"; RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getDuration()); + assertThat(opts.getDuration()).isEqualTo(expected); } @Keep @@ -254,9 +252,8 @@ private static Stream testGetDurationNegative() { @ParameterizedTest @MethodSource void testGetDurationNegative(String badValue) { - assertThrows( - IllegalArgumentException.class, - () -> new RecordingOptions.Builder().duration(badValue).build()); + assertThatThrownBy(() -> new RecordingOptions.Builder().duration(badValue).build()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -279,7 +276,7 @@ void testGetRecordingOptions() { .disk("true") .duration("120 s") .build(); - assertEquals(expected, opts.getRecordingOptions()); + assertThat(opts.getRecordingOptions()).isEqualTo(expected); } @Test @@ -289,6 +286,6 @@ void testGetRecordingOptionsDefaults() { // to insure consistent behaviour. expected.put("disk", "false"); RecordingOptions opts = new RecordingOptions.Builder().build(); - assertEquals(expected, opts.getRecordingOptions()); + assertThat(opts.getRecordingOptions()).isEqualTo(expected); } } diff --git a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingTest.java b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingTest.java index ac5c781ee..715d25bcb 100644 --- a/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingTest.java +++ b/jfr-connection/src/test/java/io/opentelemetry/contrib/jfr/connection/RecordingTest.java @@ -5,12 +5,8 @@ package io.opentelemetry.contrib.jfr.connection; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import com.google.errorprone.annotations.Keep; import java.io.FileInputStream; @@ -109,8 +105,8 @@ void tearDown() { @Test void assertNewRecordingInitialValues() { try (Recording recording = flightRecorderConnection.newRecording(null, null)) { - assertEquals(Recording.State.NEW, recording.getState()); - assertEquals(-1, recording.getId()); + assertThat(recording.getState()).isEqualTo(Recording.State.NEW); + assertThat(recording.getId()).isEqualTo(-1); } catch (IOException | IllegalStateException | JfrConnectionException exception) { fail("assertNewRecordingInitialValues caught exception", exception); } @@ -120,8 +116,8 @@ void assertNewRecordingInitialValues() { void assertRecordingStartIdAndState() { try (Recording recording = flightRecorderConnection.newRecording(null, null)) { long id = recording.start(); - assertEquals(id, recording.getId()); - assertEquals(Recording.State.RECORDING, recording.getState()); + assertThat(recording.getId()).isEqualTo(id); + assertThat(recording.getState()).isEqualTo(Recording.State.RECORDING); } catch (IOException | IllegalStateException | JfrConnectionException e) { fail("assertRecordingStartIdAndState caught exception", e); } @@ -131,9 +127,9 @@ void assertRecordingStartIdAndState() { void assertRecordingStopState() { try (Recording recording = flightRecorderConnection.newRecording(null, null)) { long id = recording.start(); - assertEquals(id, recording.getId()); + assertThat(recording.getId()).isEqualTo(id); recording.stop(); - assertEquals(Recording.State.STOPPED, recording.getState()); + assertThat(recording.getState()).isEqualTo(Recording.State.STOPPED); } catch (IOException | IllegalStateException | JfrConnectionException e) { fail("assertRecordingStopState caught exception", e); } @@ -143,9 +139,9 @@ void assertRecordingStopState() { void assertRecordingCloseState() { try (Recording recording = flightRecorderConnection.newRecording(null, null)) { long id = recording.start(); - assertEquals(id, recording.getId()); + assertThat(recording.getId()).isEqualTo(id); recording.close(); - assertEquals(Recording.State.CLOSED, recording.getState()); + assertThat(recording.getState()).isEqualTo(Recording.State.CLOSED); } catch (IOException | IllegalStateException | JfrConnectionException e) { fail("assertRecordingCloseState caught exception", e); } @@ -255,7 +251,7 @@ void assertInvalidStateChangeThrowsIllegalStateException( try (Recording recording = flightRecorderConnection.newRecording(null, null)) { reflectivelyInvokeMethods(recording, args); } catch (InvocationTargetException invocationTargetException) { - assertTrue(invocationTargetException.getCause() instanceof IllegalStateException); + assertThat(invocationTargetException.getCause()).isInstanceOf(IllegalStateException.class); } catch (Exception e) { fail("Bad test code", e); } @@ -322,7 +318,7 @@ void assertRecordingOptionsAreSetInFlightRecorderMXBean( "getRecordingOptions", new Object[] {id}, new String[] {long.class.getName()}); - assertFalse(flightRecorderMXBeanOptions.isEmpty()); + assertThat(flightRecorderMXBeanOptions.isEmpty()).isFalse(); ((Collection) flightRecorderMXBeanOptions.values()) .forEach( compositeData -> { @@ -344,7 +340,7 @@ void assertRecordingOptionsAreSetInFlightRecorderMXBean( // and for destination since FlightRecorderMXBean returns null as default if (!("name".equals(key) && "".equals(actual)) && !("destination".equals(key) && "".equals(actual))) { - assertEquals(expected, actual, getter); + assertThat(actual).as(getter).isEqualTo(expected); } } catch (NoSuchMethodException | IllegalArgumentException @@ -393,7 +389,7 @@ void assertFileExistsAfterRecordingDump() { recording.stop(); Path dumpFile = Paths.get(System.getProperty("user.dir"), "testRecordingDump_dumped.jfr"); recording.dump(dumpFile.toString()); - assertTrue(Files.exists(dumpFile)); + assertThat(dumpFile).exists(); } catch (IllegalArgumentException badData) { fail("Issue in test data: " + badData.getMessage()); } catch (IOException ioe) { @@ -428,7 +424,7 @@ void assertFileExistsAfterRecordingStream() { fail(e.getMessage(), e); } - assertTrue(Files.exists(streamedFile)); + assertThat(streamedFile).exists(); } catch (IllegalArgumentException badData) { fail("Issue in test data: " + badData.getMessage()); @@ -502,9 +498,9 @@ void assertRecordingCloneState() { try (Recording recording = flightRecorderConnection.newRecording(recordingOptions, null)) { recording.start(); Recording clone = recording.clone(true); - assertSame(recording.getState(), Recording.State.RECORDING); - assertSame(clone.getState(), Recording.State.STOPPED); - assertNotEquals(recording.getId(), clone.getId()); + assertThat(recording.getState()).isEqualTo(Recording.State.RECORDING); + assertThat(clone.getState()).isEqualTo(Recording.State.STOPPED); + assertThat(recording.getId()).isNotEqualTo(clone.getId()); recording.stop(); } catch (IOException ioe) { // possible that this can be thrown, but should not happen in this context diff --git a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilderTest.java b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilderTest.java index a7a5239ee..8c520514e 100644 --- a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilderTest.java +++ b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilderTest.java @@ -9,8 +9,8 @@ import static org.apache.kafka.clients.CommonClientConfigs.CLIENT_ID_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG; import static org.apache.kafka.clients.producer.ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.trace.data.SpanData; @@ -49,7 +49,7 @@ void buildWithSerializersInSetters() { .build()) .build(); - assertNotNull(actual); + assertThat(actual).isNotNull(); actual.close(); } @@ -74,7 +74,7 @@ void buildWithSerializersInConfig() { .build()) .build(); - assertNotNull(actual); + assertThat(actual).isNotNull(); actual.close(); } @@ -91,33 +91,32 @@ void buildWithMissingTopic() { VALUE_SERIALIZER_CLASS_CONFIG, valueSerializerMock.getClass().getName()); - assertThrows( - IllegalArgumentException.class, - () -> - new KafkaSpanExporterBuilder() - .setProducer( - KafkaSpanExporterBuilder.ProducerBuilder.newInstance() - .setConfig(producerConfig) - .build()) - .build()); + assertThatThrownBy( + () -> + new KafkaSpanExporterBuilder() + .setProducer( + KafkaSpanExporterBuilder.ProducerBuilder.newInstance() + .setConfig(producerConfig) + .build()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test void buildWithMissingProducer() { - assertThrows( - IllegalArgumentException.class, - () -> new KafkaSpanExporterBuilder().setTopicName("a-topic").build()); + assertThatThrownBy(() -> new KafkaSpanExporterBuilder().setTopicName("a-topic").build()) + .isInstanceOf(IllegalArgumentException.class); } @Test void buildWithMissingProducerConfig() { - assertThrows( - IllegalArgumentException.class, - () -> - new KafkaSpanExporterBuilder() - .setTopicName("a-topic") - .setProducer(KafkaSpanExporterBuilder.ProducerBuilder.newInstance().build()) - .build()); + assertThatThrownBy( + () -> + new KafkaSpanExporterBuilder() + .setTopicName("a-topic") + .setProducer(KafkaSpanExporterBuilder.ProducerBuilder.newInstance().build()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -129,16 +128,16 @@ void buildWithMissingSerializers() { ProducerConfig.CLIENT_ID_CONFIG, "some clientId"); - assertThrows( - IllegalArgumentException.class, - () -> - new KafkaSpanExporterBuilder() - .setTopicName("a-topic") - .setProducer( - KafkaSpanExporterBuilder.ProducerBuilder.newInstance() - .setConfig(producerConfig) - .build()) - .build()); + assertThatThrownBy( + () -> + new KafkaSpanExporterBuilder() + .setTopicName("a-topic") + .setProducer( + KafkaSpanExporterBuilder.ProducerBuilder.newInstance() + .setConfig(producerConfig) + .build()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -152,17 +151,17 @@ void buildWithKeySerializerInConfigAndValueSerializerInSetter() { KEY_SERIALIZER_CLASS_CONFIG, keySerializerMock.getClass().getName()); - assertThrows( - IllegalArgumentException.class, - () -> - new KafkaSpanExporterBuilder() - .setTopicName("a-topic") - .setProducer( - KafkaSpanExporterBuilder.ProducerBuilder.newInstance() - .setConfig(producerConfig) - .setValueSerializer(valueSerializerMock) - .build()) - .build()); + assertThatThrownBy( + () -> + new KafkaSpanExporterBuilder() + .setTopicName("a-topic") + .setProducer( + KafkaSpanExporterBuilder.ProducerBuilder.newInstance() + .setConfig(producerConfig) + .setValueSerializer(valueSerializerMock) + .build()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -176,17 +175,17 @@ void buildWithValueSerializerInConfigAndKeySerializerInSetter() { VALUE_SERIALIZER_CLASS_CONFIG, valueSerializerMock.getClass().getName()); - assertThrows( - IllegalArgumentException.class, - () -> - new KafkaSpanExporterBuilder() - .setTopicName("a-topic") - .setProducer( - KafkaSpanExporterBuilder.ProducerBuilder.newInstance() - .setConfig(producerConfig) - .setKeySerializer(keySerializerMock) - .build()) - .build()); + assertThatThrownBy( + () -> + new KafkaSpanExporterBuilder() + .setTopicName("a-topic") + .setProducer( + KafkaSpanExporterBuilder.ProducerBuilder.newInstance() + .setConfig(producerConfig) + .setKeySerializer(keySerializerMock) + .build()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } @Test @@ -202,17 +201,17 @@ void buildWithSerializersInConfigAndSetters() { VALUE_SERIALIZER_CLASS_CONFIG, valueSerializerMock.getClass().getName()); - assertThrows( - IllegalArgumentException.class, - () -> - new KafkaSpanExporterBuilder() - .setTopicName("a-topic") - .setProducer( - KafkaSpanExporterBuilder.ProducerBuilder.newInstance() - .setConfig(producerConfig) - .setKeySerializer(keySerializerMock) - .setValueSerializer(valueSerializerMock) - .build()) - .build()); + assertThatThrownBy( + () -> + new KafkaSpanExporterBuilder() + .setTopicName("a-topic") + .setProducer( + KafkaSpanExporterBuilder.ProducerBuilder.newInstance() + .setConfig(producerConfig) + .setKeySerializer(keySerializerMock) + .setValueSerializer(valueSerializerMock) + .build()) + .build()) + .isInstanceOf(IllegalArgumentException.class); } } diff --git a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataDeserializerTest.java b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataDeserializerTest.java index 395fd357d..46636a247 100644 --- a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataDeserializerTest.java +++ b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataDeserializerTest.java @@ -5,9 +5,7 @@ package io.opentelemetry.contrib.kafka; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.proto.resource.v1.Resource; @@ -38,16 +36,16 @@ void deserialize() { ExportTraceServiceRequest actual = testSubject.deserialize("test-topic", data); - assertEquals(request, actual); + assertThat(actual).isEqualTo(request); } @Test void deserializeNullData() { - assertNull(testSubject.deserialize("test-topic", null)); + assertThat(testSubject.deserialize("test-topic", null)).isNull(); } @Test void deserializeEmptyData() { - assertNotNull(testSubject.deserialize("test-topic", new byte[0])); + assertThat(testSubject.deserialize("test-topic", new byte[0])).isNotNull(); } } diff --git a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataSerializerTest.java b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataSerializerTest.java index 517610724..06c3df63e 100644 --- a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataSerializerTest.java +++ b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/SpanDataSerializerTest.java @@ -6,8 +6,7 @@ package io.opentelemetry.contrib.kafka; import static io.opentelemetry.contrib.kafka.TestUtil.makeBasicSpan; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.assertj.core.api.Assertions.assertThat; import com.google.common.collect.ImmutableList; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; @@ -28,14 +27,14 @@ void serialize() { byte[] actual = testSubject.serialize("test-topic", spans); - assertNotNull(actual); + assertThat(actual).isNotNull(); } @Test void serializeEmptyData() { byte[] actual = testSubject.serialize("test-topic", Collections.emptySet()); - assertEquals(0, actual.length); + assertThat(actual).isEmpty(); } @Test @@ -46,16 +45,18 @@ void convertSpansToRequest() { ExportTraceServiceRequest actual = testSubject.convertSpansToRequest(spans); - assertNotNull(actual); - assertEquals("span-1", actual.getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()); - assertEquals("span-2", actual.getResourceSpans(0).getScopeSpans(0).getSpans(1).getName()); + assertThat(actual).isNotNull(); + assertThat(actual.getResourceSpans(0).getScopeSpans(0).getSpans(0).getName()) + .isEqualTo("span-1"); + assertThat(actual.getResourceSpans(0).getScopeSpans(0).getSpans(1).getName()) + .isEqualTo("span-2"); } @Test void convertSpansToRequestForEmptySpans() { ExportTraceServiceRequest actual = testSubject.convertSpansToRequest(Collections.emptySet()); - assertNotNull(actual); - assertEquals(ExportTraceServiceRequest.getDefaultInstance(), actual); + assertThat(actual).isNotNull(); + assertThat(actual).isEqualTo(ExportTraceServiceRequest.getDefaultInstance()); } } diff --git a/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java b/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java index 50405d454..874895340 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.filter; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.trace.Span; @@ -93,14 +93,14 @@ void verifyLogFilteringExistSpanContext() { sdk.getLogsBridge().get("test").logRecordBuilder().setBody("One Log").emit(); List finishedLogRecordItems = memoryLogRecordExporter.getFinishedLogRecordItems(); - assertEquals(1, finishedLogRecordItems.size()); + assertThat(finishedLogRecordItems.size()).isEqualTo(1); try (Scope scope = span.makeCurrent()) { } finally { span.end(); } List finishedSpans = spansExporter.getFinishedSpanItems(); - assertEquals(1, finishedSpans.size()); + assertThat(finishedSpans.size()).isEqualTo(1); } } @@ -109,6 +109,6 @@ void verifyFilteringNotExitSpanContext() { logger.logRecordBuilder().setBody("One Log").emit(); List finishedLogRecordItems = memoryLogRecordExporter.getFinishedLogRecordItems(); - assertEquals(0, finishedLogRecordItems.size()); + assertThat(finishedLogRecordItems.size()).isEqualTo(0); } } diff --git a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableLogRecordExporterTest.java b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableLogRecordExporterTest.java index 3b81ce277..0096caa66 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableLogRecordExporterTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableLogRecordExporterTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.interceptor; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -62,13 +62,13 @@ void verifyLogModification() { List finishedLogRecordItems = memoryLogRecordExporter.getFinishedLogRecordItems(); - assertEquals(1, finishedLogRecordItems.size()); + assertThat(finishedLogRecordItems.size()).isEqualTo(1); LogRecordData logRecordData = finishedLogRecordItems.get(0); - assertEquals(2, logRecordData.getAttributes().size()); - assertEquals( - "from interceptor", - logRecordData.getAttributes().get(AttributeKey.stringKey("global.attr"))); - assertEquals("local", logRecordData.getAttributes().get(AttributeKey.stringKey("local.attr"))); + assertThat(logRecordData.getAttributes().size()).isEqualTo(2); + assertThat(logRecordData.getAttributes().get(AttributeKey.stringKey("global.attr"))) + .isEqualTo("from interceptor"); + assertThat(logRecordData.getAttributes().get(AttributeKey.stringKey("local.attr"))) + .isEqualTo("local"); } @Test @@ -87,9 +87,9 @@ void verifyLogFiltering() { List finishedLogRecordItems = memoryLogRecordExporter.getFinishedLogRecordItems(); - assertEquals(2, finishedLogRecordItems.size()); - assertEquals(Value.of("One log"), finishedLogRecordItems.get(0).getBodyValue()); - assertEquals(Value.of("Another log"), finishedLogRecordItems.get(1).getBodyValue()); + assertThat(finishedLogRecordItems.size()).isEqualTo(2); + assertThat(finishedLogRecordItems.get(0).getBodyValue()).isEqualTo(Value.of("One log")); + assertThat(finishedLogRecordItems.get(1).getBodyValue()).isEqualTo(Value.of("Another log")); } private static class ModifiableLogRecordData implements LogRecordData { diff --git a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableMetricExporterTest.java b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableMetricExporterTest.java index 6b12d5f1a..f321b8a7b 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableMetricExporterTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableMetricExporterTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.interceptor; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.contrib.interceptor.common.ComposableInterceptor; @@ -55,8 +54,8 @@ void verifyMetricModification() { meterProvider.forceFlush(); List finishedMetricItems = memoryMetricExporter.getFinishedMetricItems(); - assertEquals(1, finishedMetricItems.size()); - assertEquals("ModifiedName", finishedMetricItems.get(0).getName()); + assertThat(finishedMetricItems.size()).isEqualTo(1); + assertThat(finishedMetricItems.get(0).getName()).isEqualTo("ModifiedName"); } @Test @@ -75,7 +74,7 @@ void verifyMetricFiltering() { meterProvider.forceFlush(); List finishedMetricItems = memoryMetricExporter.getFinishedMetricItems(); - assertEquals(2, finishedMetricItems.size()); + assertThat(finishedMetricItems.size()).isEqualTo(2); List names = new ArrayList<>(); for (MetricData item : finishedMetricItems) { names.add(item.getName()); diff --git a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java index a6c177181..26242174f 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/interceptor/InterceptableSpanExporterTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.interceptor; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -51,12 +51,12 @@ void verifySpanModification() { tracer.spanBuilder("Test span").setAttribute("local.attr", 10).startSpan().end(); List finishedSpanItems = memorySpanExporter.getFinishedSpanItems(); - assertEquals(1, finishedSpanItems.size()); + assertThat(finishedSpanItems.size()).isEqualTo(1); SpanData spanData = finishedSpanItems.get(0); - assertEquals(2, spanData.getAttributes().size()); - assertEquals( - "from interceptor", spanData.getAttributes().get(AttributeKey.stringKey("global.attr"))); - assertEquals(10, spanData.getAttributes().get(AttributeKey.longKey("local.attr"))); + assertThat(spanData.getAttributes().size()).isEqualTo(2); + assertThat(spanData.getAttributes().get(AttributeKey.stringKey("global.attr"))) + .isEqualTo("from interceptor"); + assertThat(spanData.getAttributes().get(AttributeKey.longKey("local.attr"))).isEqualTo(10L); } @Test @@ -74,9 +74,9 @@ void verifySpanFiltering() { tracer.spanBuilder("Another span").startSpan().end(); List finishedSpanItems = memorySpanExporter.getFinishedSpanItems(); - assertEquals(2, finishedSpanItems.size()); - assertEquals("One span", finishedSpanItems.get(0).getName()); - assertEquals("Another span", finishedSpanItems.get(1).getName()); + assertThat(finishedSpanItems.size()).isEqualTo(2); + assertThat(finishedSpanItems.get(0).getName()).isEqualTo("One span"); + assertThat(finishedSpanItems.get(1).getName()).isEqualTo("Another span"); } private static class ModifiableSpanData extends DelegatingSpanData { diff --git a/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java b/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java index b48f4685a..5a63d28e7 100644 --- a/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java +++ b/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java @@ -6,8 +6,7 @@ package io.opentelemetry.contrib.resourceproviders; import static io.opentelemetry.contrib.resourceproviders.JettyAppServer.parseJettyBase; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; +import static org.assertj.core.api.Assertions.assertThat; import java.io.IOException; import java.nio.file.Files; @@ -19,17 +18,17 @@ public class JettyServiceNameDetectorTest { @Test void testJettyBase(@TempDir Path tempDir) throws IOException { - assertNull(parseJettyBase(null)); - assertNull(parseJettyBase("")); - assertNull(parseJettyBase("jetty.base=")); - assertEquals(tempDir.toString(), parseJettyBase("jetty.base=" + tempDir).toString()); - assertEquals( - tempDir.toString(), parseJettyBase("foo jetty.base=" + tempDir + " bar").toString()); + assertThat(parseJettyBase(null)).isNull(); + assertThat(parseJettyBase("")).isNull(); + assertThat(parseJettyBase("jetty.base=")).isNull(); + assertThat(parseJettyBase("jetty.base=" + tempDir).toString()).isEqualTo(tempDir.toString()); + assertThat(parseJettyBase("foo jetty.base=" + tempDir + " bar").toString()) + .isEqualTo(tempDir.toString()); Path otherDir = tempDir.resolve("jetty test"); Files.createDirectory(otherDir); - assertEquals(otherDir.toString(), parseJettyBase("jetty.base=" + otherDir).toString()); - assertEquals( - otherDir.toString(), parseJettyBase("foo jetty.base=" + otherDir + " bar").toString()); + assertThat(parseJettyBase("jetty.base=" + otherDir).toString()).isEqualTo(otherDir.toString()); + assertThat(parseJettyBase("foo jetty.base=" + otherDir + " bar").toString()) + .isEqualTo(otherDir.toString()); } } From b34cd92701f2db53af3d64fdf95fc64b385e07bb Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 26 Aug 2025 15:57:25 -0700 Subject: [PATCH 227/371] fix(deps): update semconvversion (#2189) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 58d3e04d7..dbbaa29c7 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } val otelInstrumentationVersion = "2.19.0-alpha" -val semconvVersion = "1.34.0" +val semconvVersion = "1.36.0" javaPlatform { allowDependencies() From 9e432e86455a331ce25446b95400057eaa93f964 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 27 Aug 2025 07:43:21 -0700 Subject: [PATCH 228/371] Sync with instrumentation workflows (#2168) --- .github/workflows/build-common.yml | 45 +++++++++++++++------------ .github/workflows/build.yml | 2 ++ .github/workflows/codeql.yml | 8 ++--- .github/workflows/release.yml | 50 ++---------------------------- 4 files changed, 34 insertions(+), 71 deletions(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 75490653b..002becb24 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -18,6 +18,25 @@ permissions: contents: read jobs: + spotless: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - name: Set up JDK for running Gradle + uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + with: + distribution: temurin + java-version: 17 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + with: + cache-read-only: ${{ inputs.cache-read-only }} + + - name: Spotless + run: ./gradlew spotlessCheck ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + build: runs-on: ubuntu-latest steps: @@ -29,13 +48,13 @@ jobs: distribution: temurin java-version: 17 - - name: Set up gradle + - name: Set up Gradle uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: ${{ inputs.cache-read-only }} - - name: Gradle build and test - run: ./gradlew build -x test ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + - name: Build + run: ./gradlew build -x spotlessCheck -x test ${{ inputs.no-build-cache && '--no-build-cache' || '' }} test: name: Test @@ -44,8 +63,6 @@ jobs: fail-fast: false matrix: os: - - macos-latest - - macos-13 - ubuntu-latest - windows-latest test-java-version: @@ -54,18 +71,6 @@ jobs: - 17 - 21 - 24 # renovate: datasource=java-version - # macos-latest drops support for java 8 temurin. Run java 8 on macos-13. Run java 11, 17, 21 on macos-latest. - exclude: - - os: macos-latest - test-java-version: 8 - - os: macos-13 - test-java-version: 11 - - os: macos-13 - test-java-version: 17 - - os: macos-13 - test-java-version: 21 - - os: macos-13 - test-java-version: 24 # renovate: datasource=java-version steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 @@ -83,12 +88,12 @@ jobs: distribution: temurin java-version: 17 - - name: Set up gradle + - name: Set up Gradle uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: ${{ inputs.cache-read-only }} - - name: Gradle test + - name: Test run: > ./gradlew test "-PtestJavaVersion=${{ matrix.test-java-version }}" @@ -108,7 +113,7 @@ jobs: distribution: temurin java-version: 17 - - name: Set up gradle + - name: Set up Gradle uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 with: cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b93eceaea..558f3804f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,6 +21,8 @@ jobs: needs: - common runs-on: ubuntu-latest + # skipping release branches because the versions in those branches are not snapshots + if: github.ref_name == 'main' && github.repository == 'open-telemetry/opentelemetry-java-contrib' steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 0ff8fbe7a..61b90c7f0 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -1,10 +1,6 @@ name: CodeQL on: - push: - branches: - - main - - release/* pull_request: branches: - main @@ -15,6 +11,10 @@ on: # - https://github.com/github/codeql-action/issues/1537 # - https://github.com/github/codeql-action/issues/2691 # merge_group: + push: + branches: + - main + - release/* schedule: - cron: "29 13 * * 2" # weekly at 13:29 UTC on Tuesday diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7d9f1827f..ba27da1d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,59 +11,15 @@ permissions: contents: read jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Set up JDK for running Gradle - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 - with: - distribution: temurin - java-version: 17 - - - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - - name: Gradle build - run: ./gradlew build - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - name: Save unit test results - if: always() - with: - name: test-results - path: jmx-metrics/build/reports/tests/test - - integration-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Set up JDK for running Gradle - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 - with: - distribution: temurin - java-version: 17 - - - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - - name: Integration test - run: ./gradlew integrationTest - - - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - name: Save integration test results - if: always() - with: - name: integration-test-results - path: jmx-metrics/build/reports/tests/integrationTest + common: + uses: ./.github/workflows/build-common.yml release: permissions: contents: write # for creating the release runs-on: ubuntu-latest needs: - - build - - integration-test + - common outputs: version: ${{ steps.create-github-release.outputs.version }} steps: From 1ac0bcfed4687239214d6fe48af2d6d2bb751702 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 27 Aug 2025 15:39:24 -0700 Subject: [PATCH 229/371] fix(deps): update semconvversion (#2191) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index dbbaa29c7..641c62207 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -3,7 +3,7 @@ plugins { } val otelInstrumentationVersion = "2.19.0-alpha" -val semconvVersion = "1.36.0" +val semconvVersion = "1.37.0" javaPlatform { allowDependencies() From 83550c8431c507b116393467b75934baf1f6bab2 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 29 Aug 2025 01:10:11 -0700 Subject: [PATCH 230/371] Fix the build (#2193) Co-authored-by: Lauri Tulmin --- .github/renovate.json5 | 10 ++++++++++ .../target_systems/KafkaIntegrationTest.java | 6 +++--- .../kafka/KafkaContainerFactory.java | 2 +- opamp-client/build.gradle.kts | 16 +--------------- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index df236f85e..5b8203753 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -202,5 +202,15 @@ depNameTemplate: 'java', extractVersionTemplate: '^(?\\d+)', }, + { + customType: 'regex', + datasourceTemplate: 'github-releases', + managerFilePatterns: [ + '**/build.gradle.kts', + ], + matchStrings: [ + '"https://github.com/(?[^/]+/[^/]+)/zipball/(?.+?)"', + ], + }, ], } diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/KafkaIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/KafkaIntegrationTest.java index 4c2c9293c..4dddd975a 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/KafkaIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/KafkaIntegrationTest.java @@ -44,7 +44,7 @@ protected KafkaIntegrationTest(String configName) { @Container GenericContainer kafka = - new GenericContainer<>("bitnami/kafka:2.8.1") + new GenericContainer<>("bitnamilegacy/kafka:2.8.1") .withNetwork(Network.SHARED) .withEnv("KAFKA_CFG_ZOOKEEPER_CONNECT", "zookeeper:2181") .withEnv("ALLOW_PLAINTEXT_LISTENER", "yes") @@ -80,7 +80,7 @@ public Set getDependencies() { }; protected GenericContainer kafkaProducerContainer() { - return new GenericContainer<>("bitnami/kafka:2.8.1") + return new GenericContainer<>("bitnamilegacy/kafka:2.8.1") .withNetwork(Network.SHARED) .withEnv("KAFKA_CFG_ZOOKEEPER_CONNECT", "zookeeper:2181") .withEnv("ALLOW_PLAINTEXT_LISTENER", "yes") @@ -207,7 +207,7 @@ static class KafkaConsumerIntegrationTest extends KafkaIntegrationTest { @Container GenericContainer consumer = - new GenericContainer<>("bitnami/kafka:2.8.1") + new GenericContainer<>("bitnamilegacy/kafka:2.8.1") .withNetwork(Network.SHARED) .withEnv("KAFKA_CFG_ZOOKEEPER_CONNECT", "zookeeper:2181") .withEnv("ALLOW_PLAINTEXT_LISTENER", "yes") diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaContainerFactory.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaContainerFactory.java index 8eb9432a5..e46ed07b6 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaContainerFactory.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaContainerFactory.java @@ -12,7 +12,7 @@ public class KafkaContainerFactory { private static final int KAFKA_PORT = 9092; private static final String KAFKA_BROKER = "kafka:" + KAFKA_PORT; - private static final String KAFKA_DOCKER_IMAGE = "bitnami/kafka:2.8.1"; + private static final String KAFKA_DOCKER_IMAGE = "bitnamilegacy/kafka:2.8.1"; private KafkaContainerFactory() {} diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 1aeed0c40..47e64f778 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -1,6 +1,4 @@ import de.undercouch.gradle.tasks.download.DownloadExtension -import java.net.HttpURLConnection -import java.net.URL plugins { id("otel.java-conventions") @@ -53,19 +51,7 @@ abstract class DownloadOpampProtos @Inject constructor( @TaskAction fun execute() { - // Get the latest release tag by following the redirect from GitHub's latest release URL - val latestReleaseUrl = "https://github.com/open-telemetry/opamp-spec/releases/latest" - val connection = URL(latestReleaseUrl).openConnection() as HttpURLConnection - connection.instanceFollowRedirects = false - connection.requestMethod = "HEAD" - - val redirectLocation = connection.getHeaderField("Location") - connection.disconnect() - - // Extract tag from URL like: https://github.com/open-telemetry/opamp-spec/releases/tag/v0.12.0 - val latestTag = redirectLocation.substringAfterLast("/") - // Download the source code for the latest release - val zipUrl = "https://github.com/open-telemetry/opamp-spec/zipball/$latestTag" + val zipUrl = "https://github.com/open-telemetry/opamp-spec/zipball/v0.14.0" download.run { src(zipUrl) From d71dab4c143be4ef39bbba401f7604097288802c Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Fri, 29 Aug 2025 16:41:15 +0300 Subject: [PATCH 231/371] Add workflow step that shows build scan link (#2195) --- .github/workflows/build-common.yml | 8 ++++++++ settings.gradle.kts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 002becb24..3ad7b8f5d 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -102,6 +102,10 @@ jobs: "-PmaxTestRetries=${{ inputs.max-test-retries }}" ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + - name: Build scan + if: ${{ !cancelled() && hashFiles('build-scan.txt') != '' }} + run: cat build-scan.txt + integration-test: runs-on: ubuntu-latest steps: @@ -121,6 +125,10 @@ jobs: - name: Integration test run: ./gradlew integrationTest "-PmaxTestRetries=${{ inputs.max-test-retries }}" ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + - name: Build scan + if: ${{ !cancelled() && hashFiles('build-scan.txt') != '' }} + run: cat build-scan.txt + - name: Save integration test results uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: always() diff --git a/settings.gradle.kts b/settings.gradle.kts index 0ea557d7d..7d9db17b1 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -29,6 +29,12 @@ develocity { publishing.onlyIf { System.getenv("CI") != null } termsOfUseUrl.set("https://gradle.com/help/legal-terms-of-use") termsOfUseAgree.set("yes") + + buildScanPublished { + File("build-scan.txt").printWriter().use { writer -> + writer.println(buildScanUri) + } + } } } From fae25518852b95dfc7f45781c4b507e98fe2e6e2 Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 29 Aug 2025 22:48:03 +0200 Subject: [PATCH 232/371] add link to jmx scraper + minor rewording (#2151) Co-authored-by: Lauri Tulmin --- jmx-scraper/README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index a2f98f5b1..5a3b496a2 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -9,13 +9,18 @@ This is an alternative to the [JMX Gatherer](../jmx-metrics/README.md) utility. ## Release This project is released as part of the [OpenTelemetry Java Contrib](https://github.com/open-telemetry/opentelemetry-java-contrib) project. -The latest release is available from [Maven Central](https://central.sonatype.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper) -or can be browsed via [MVN Repository](https://mvnrepository.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper). +The latest release is available from: + +- [GitHub Release assets](https://github.com/open-telemetry/opentelemetry-java-contrib/releases/latest/download/opentelemetry-jmx-scraper.jar) +- [Maven Central](https://central.sonatype.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper) or can be browsed via [MVN Repository](https://mvnrepository.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper). ## Usage The general command to invoke JMX scraper is `java -jar scraper.jar `, where `scraper.jar` -is the `build/libs/opentelemetry-jmx-scraper-.jar` packaged binary when building this module. +is the packaged binary: + +- `build/libs/opentelemetry-jmx-scraper-.jar` when building from sources +- `.jar` file downloaded from Maven central or Release assets Minimal configuration required From b7c2b76c814501761cad2ddb78a6a056c41d02e1 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Sun, 31 Aug 2025 11:14:30 -0400 Subject: [PATCH 233/371] Fix GCP Integration test (#2197) --- gcp-auth-extension/build.gradle.kts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 0f8b121d9..f378df0d9 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -103,6 +103,9 @@ tasks.register("copyAgent") { } tasks.register("IntegrationTestUserCreds") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + dependsOn(tasks.shadowJar) dependsOn(tasks.named("copyAgent")) From 088e72dcd50e65537be5a4ff6abdba7004459888 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 08:14:58 +0300 Subject: [PATCH 234/371] fix(deps): update all patch versions (#2198) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- ibm-mq-metrics/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index e3639a9d5..a02e2db1c 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -151,7 +151,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) - implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.1")) + implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.2")) compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 7e3afe148..3b43769c2 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -31,7 +31,7 @@ val ibmClientJar: Configuration by configurations.creating { dependencies { api("com.google.code.findbugs:jsr305:3.0.2") api("io.swagger:swagger-annotations:1.6.16") - api("org.jetbrains:annotations:26.0.2") + api("org.jetbrains:annotations:26.0.2-1") api("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0") api("org.yaml:snakeyaml:2.4") api("com.fasterxml.jackson.core:jackson-databind:2.19.2") From 712ad7c77780be8e7d6e843b0c36322fb1ba48bd Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 2 Sep 2025 11:04:11 -0700 Subject: [PATCH 235/371] "Applying" potential Copilot code review rules (#2182) --- .../aws/resource/BeanstalkResource.java | 6 +-- .../contrib/aws/resource/DockerHelper.java | 7 +-- .../contrib/aws/resource/Ec2Resource.java | 4 +- .../contrib/aws/resource/EcsResource.java | 17 +++---- .../contrib/aws/resource/EksResource.java | 9 ++-- .../aws/resource/SimpleHttpClient.java | 14 +++--- .../propagator/AwsXrayLambdaPropagator.java | 5 +- .../awsxray/propagator/AwsXrayPropagator.java | 4 +- .../contrib/awsxray/AwsAttributeKeys.java | 25 +++++----- .../awsxray/AwsMetricAttributeGenerator.java | 47 +++++++++---------- .../awsxray/AwsSpanMetricsProcessor.java | 5 +- .../contrib/awsxray/AwsXrayRemoteSampler.java | 36 +++++++------- .../awsxray/AwsXrayRemoteSamplerBuilder.java | 6 ++- .../contrib/awsxray/SamplingRuleApplier.java | 27 +++++------ .../contrib/awsxray/XrayRulesSampler.java | 9 ++-- .../contrib/awsxray/XraySamplerClient.java | 5 +- azure-resources/build.gradle.kts | 4 +- .../AzureAppServiceResourceProvider.java | 4 +- .../azure/resource/AzureMetadataService.java | 15 +++--- .../resource/AzureVmResourceProvider.java | 28 +++++------ .../AzureAppServiceResourceProviderTest.java | 4 +- .../AzureContainersResourceProviderTest.java | 4 +- .../AzureFunctionsResourceProviderTest.java | 5 +- .../MetadataBasedResourceProviderTest.java | 4 +- .../processor/BaggageLogRecordProcessor.java | 16 +++---- .../resources/CloudFoundryResource.java | 22 ++++----- .../resources/CloudFoundryResourceTest.java | 10 ++-- .../config/StorageConfiguration.java | 10 ++-- .../exporters/SignalStorageExporter.java | 5 +- .../exporters/SpanToDiskExporter.java | 13 ++--- .../internal/exporter/ToDiskExporter.java | 8 ++-- .../internal/storage/StorageBuilder.java | 5 +- .../buffering/internal/utils/DebugLogger.java | 4 +- .../contrib/gcp/auth/ConfigurableOption.java | 6 +-- ...thAutoConfigurationCustomizerProvider.java | 19 ++++---- ...toConfigurationCustomizerProviderTest.java | 11 +++-- .../gcp/resource/GCPResourceProvider.java | 1 + .../gcp/resource/IncubatingAttributes.java | 7 +-- .../ibm/mq/metricscollector/MetricAssert.java | 7 +-- .../InferredSpansAutoConfig.java | 5 +- .../inferredspans/InferredSpansProcessor.java | 7 ++- .../InferredSpansProcessorBuilder.java | 7 ++- .../inferredspans/WildcardMatcher.java | 5 +- .../jfrevent/JfrSpanProcessorTest.java | 6 +-- .../jmxscraper/JmxConnectorBuilder.java | 5 +- .../contrib/jmxscraper/JmxScraper.java | 34 +++++++------- .../jmxscraper/config/JmxScraperConfig.java | 18 ++++--- .../contrib/jmxscraper/JmxScraperTest.java | 12 ++--- .../metrics/micrometer/MicrometerMeter.java | 11 +++-- .../micrometer/MicrometerMeterProvider.java | 9 ++-- .../ScheduledCallbackRegistrar.java | 5 +- .../ScheduledCallbackRegistrarBuilder.java | 7 +-- .../websocket/OkHttpWebSocket.java | 5 +- .../client/internal/state/InMemoryState.java | 5 +- .../FilteringLogRecordProcessorTest.java | 2 +- .../clientbridge/MetricAdapter.java | 13 ++--- .../clientbridge/PrometheusCollector.java | 5 +- .../prometheus/clientbridge/Serializer.java | 5 +- .../stacktrace/StackTraceAutoConfig.java | 7 ++- 59 files changed, 324 insertions(+), 287 deletions(-) diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/BeanstalkResource.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/BeanstalkResource.java index 9f294d4fb..7441b7c81 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/BeanstalkResource.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/BeanstalkResource.java @@ -12,6 +12,7 @@ import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.SERVICE_INSTANCE_ID; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.SERVICE_NAMESPACE; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION; +import static java.util.logging.Level.WARNING; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; @@ -22,7 +23,6 @@ import io.opentelemetry.semconv.SchemaUrls; import java.io.File; import java.io.IOException; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -65,7 +65,7 @@ static Resource buildResource(String configPath) { parser.nextToken(); if (!parser.isExpectedStartObjectToken()) { - logger.log(Level.WARNING, "Invalid Beanstalk config: ", configPath); + logger.log(WARNING, "Invalid Beanstalk config: ", configPath); return Resource.create(attrBuilders.build(), SchemaUrls.V1_25_0); } @@ -87,7 +87,7 @@ static Resource buildResource(String configPath) { } } } catch (IOException e) { - logger.log(Level.WARNING, "Could not parse Beanstalk config.", e); + logger.log(WARNING, "Could not parse Beanstalk config.", e); return Resource.empty(); } diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/DockerHelper.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/DockerHelper.java index 5b8aefeac..a52cf1c18 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/DockerHelper.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/DockerHelper.java @@ -5,11 +5,12 @@ package io.opentelemetry.contrib.aws.resource; +import static java.util.logging.Level.WARNING; + import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; -import java.util.logging.Level; import java.util.logging.Logger; class DockerHelper { @@ -44,9 +45,9 @@ public String getContainerId() { } } } catch (FileNotFoundException e) { - logger.log(Level.WARNING, "Failed to read container id, cgroup file does not exist."); + logger.log(WARNING, "Failed to read container id, cgroup file does not exist."); } catch (IOException e) { - logger.log(Level.WARNING, "Unable to read container id: " + e.getMessage()); + logger.log(WARNING, "Unable to read container id: " + e.getMessage()); } return ""; diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/Ec2Resource.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/Ec2Resource.java index d4bdb4228..5aa930b1c 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/Ec2Resource.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/Ec2Resource.java @@ -15,6 +15,7 @@ import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.HOST_IMAGE_ID; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.HOST_NAME; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.HOST_TYPE; +import static java.util.logging.Level.WARNING; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; @@ -28,7 +29,6 @@ import java.net.URL; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -125,7 +125,7 @@ static Resource buildResource(String endpoint) { } } } catch (IOException e) { - logger.log(Level.WARNING, "Could not parse identity document, resource not filled.", e); + logger.log(WARNING, "Could not parse identity document, resource not filled.", e); return Resource.empty(); } diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java index efb112de8..83440819b 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EcsResource.java @@ -26,6 +26,9 @@ import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CONTAINER_NAME; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CloudPlatformIncubatingValues.AWS_ECS; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CloudProviderIncubatingValues.AWS; +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonList; +import static java.util.logging.Level.WARNING; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; @@ -35,11 +38,9 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.semconv.SchemaUrls; import java.io.IOException; -import java.util.Collections; import java.util.Locale; import java.util.Map; import java.util.Optional; -import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -88,7 +89,7 @@ static Resource buildResource(Map sysEnv, SimpleHttpClient httpC static void fetchMetadata( SimpleHttpClient httpClient, String url, AttributesBuilder attrBuilders) { - String json = httpClient.fetchString("GET", url, Collections.emptyMap(), null); + String json = httpClient.fetchString("GET", url, emptyMap(), null); if (json.isEmpty()) { return; } @@ -103,17 +104,17 @@ static void fetchMetadata( .getLogGroupArn() .ifPresent( logGroupArn -> { - attrBuilders.put(AWS_LOG_GROUP_ARNS, Collections.singletonList(logGroupArn)); + attrBuilders.put(AWS_LOG_GROUP_ARNS, singletonList(logGroupArn)); }); logArnBuilder .getLogStreamArn() .ifPresent( logStreamArn -> { - attrBuilders.put(AWS_LOG_STREAM_ARNS, Collections.singletonList(logStreamArn)); + attrBuilders.put(AWS_LOG_STREAM_ARNS, singletonList(logStreamArn)); }); } catch (IOException e) { - logger.log(Level.WARNING, "Can't get ECS metadata", e); + logger.log(WARNING, "Can't get ECS metadata", e); } } @@ -156,7 +157,7 @@ static void parseResponse( JsonParser parser, AttributesBuilder attrBuilders, LogArnBuilder logArnBuilder) throws IOException { if (!parser.isExpectedStartObjectToken()) { - logger.log(Level.WARNING, "Couldn't parse ECS metadata, invalid JSON"); + logger.log(WARNING, "Couldn't parse ECS metadata, invalid JSON"); return; } @@ -339,7 +340,7 @@ static DockerImage parse(@Nullable String image) { } Matcher matcher = imagePattern.matcher(image); if (!matcher.matches()) { - logger.log(Level.WARNING, "Couldn't parse image '" + image + "'"); + logger.log(WARNING, "Couldn't parse image '" + image + "'"); return null; } String repository = matcher.group("repository"); diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EksResource.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EksResource.java index 8ed3fb512..156755446 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EksResource.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/EksResource.java @@ -11,6 +11,8 @@ import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CloudPlatformIncubatingValues.AWS_EKS; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.CloudProviderIncubatingValues.AWS; import static io.opentelemetry.contrib.aws.resource.IncubatingAttributes.K8S_CLUSTER_NAME; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.WARNING; import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; @@ -26,7 +28,6 @@ import java.nio.file.Paths; import java.util.HashMap; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -91,7 +92,7 @@ static Resource buildResource( private static boolean isEks( String k8sTokenPath, String k8sKeystorePath, SimpleHttpClient httpClient) { if (!isK8s(k8sTokenPath, k8sKeystorePath)) { - logger.log(Level.FINE, "Not running on k8s."); + logger.log(FINE, "Not running on k8s."); return false; } @@ -145,7 +146,7 @@ private static String getClusterName(SimpleHttpClient httpClient) { } } } catch (IOException e) { - logger.log(Level.WARNING, "Can't get cluster name on EKS.", e); + logger.log(WARNING, "Can't get cluster name on EKS.", e); } return ""; } @@ -156,7 +157,7 @@ private static String getK8sCredHeader() { new String(Files.readAllBytes(Paths.get(K8S_TOKEN_PATH)), StandardCharsets.UTF_8); return "Bearer " + content; } catch (IOException e) { - logger.log(Level.WARNING, "Unable to load K8s client token.", e); + logger.log(WARNING, "Unable to load K8s client token.", e); } return ""; } diff --git a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/SimpleHttpClient.java b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/SimpleHttpClient.java index 12bc6e34e..f78719d99 100644 --- a/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/SimpleHttpClient.java +++ b/aws-resources/src/main/java/io/opentelemetry/contrib/aws/resource/SimpleHttpClient.java @@ -5,6 +5,9 @@ package io.opentelemetry.contrib.aws.resource; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.WARNING; + import java.io.FileInputStream; import java.io.IOException; import java.security.KeyStore; @@ -13,7 +16,6 @@ import java.time.Duration; import java.util.Collection; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.net.ssl.SSLContext; @@ -72,7 +74,7 @@ public String fetchString( int responseCode = response.code(); if (responseCode != 200) { logger.log( - Level.FINE, + FINE, "Error response from " + urlStr + " code (" @@ -84,7 +86,7 @@ public String fetchString( ResponseBody body = response.body(); return body != null ? body.string() : ""; } catch (IOException e) { - logger.log(Level.FINE, "SimpleHttpClient fetch string failed.", e); + logger.log(FINE, "SimpleHttpClient fetch string failed.", e); } return ""; @@ -101,7 +103,7 @@ private static X509TrustManager buildTrustManager(@Nullable KeyStore keyStore) { tmf.init(keyStore); return (X509TrustManager) tmf.getTrustManagers()[0]; } catch (Exception e) { - logger.log(Level.WARNING, "Build SslSocketFactory for K8s restful client exception.", e); + logger.log(WARNING, "Build SslSocketFactory for K8s restful client exception.", e); return null; } } @@ -117,7 +119,7 @@ private static SSLSocketFactory buildSslSocketFactory(@Nullable TrustManager tru return context.getSocketFactory(); } catch (Exception e) { - logger.log(Level.WARNING, "Build SslSocketFactory for K8s restful client exception.", e); + logger.log(WARNING, "Build SslSocketFactory for K8s restful client exception.", e); } return null; } @@ -138,7 +140,7 @@ private static KeyStore getKeystoreForTrustedCert(String certPath) { } return trustStore; } catch (Exception e) { - logger.log(Level.WARNING, "Cannot load KeyStore from " + certPath); + logger.log(WARNING, "Cannot load KeyStore from " + certPath); return null; } } diff --git a/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayLambdaPropagator.java b/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayLambdaPropagator.java index a6b6a2ab4..b34bc961c 100644 --- a/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayLambdaPropagator.java +++ b/aws-xray-propagator/src/main/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayLambdaPropagator.java @@ -5,12 +5,13 @@ package io.opentelemetry.contrib.awsxray.propagator; +import static java.util.Collections.singletonMap; + import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Context; import io.opentelemetry.context.propagation.TextMapGetter; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.context.propagation.TextMapSetter; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; @@ -75,7 +76,7 @@ public Context extract(Context context, @Nullable C carrier, TextMapGetter FIELDS = Collections.singletonList(TRACE_HEADER_KEY); + private static final List FIELDS = singletonList(TRACE_HEADER_KEY); private static final AwsXrayPropagator INSTANCE = new AwsXrayPropagator(); diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java index 101641f08..c9e762d63 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsAttributeKeys.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.awsxray; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + import io.opentelemetry.api.common.AttributeKey; /** Utility class holding attribute keys with special meaning to AWS components */ @@ -12,28 +14,25 @@ final class AwsAttributeKeys { private AwsAttributeKeys() {} - static final AttributeKey AWS_SPAN_KIND = AttributeKey.stringKey("aws.span.kind"); + static final AttributeKey AWS_SPAN_KIND = stringKey("aws.span.kind"); - static final AttributeKey AWS_LOCAL_SERVICE = AttributeKey.stringKey("aws.local.service"); + static final AttributeKey AWS_LOCAL_SERVICE = stringKey("aws.local.service"); - static final AttributeKey AWS_LOCAL_OPERATION = - AttributeKey.stringKey("aws.local.operation"); + static final AttributeKey AWS_LOCAL_OPERATION = stringKey("aws.local.operation"); - static final AttributeKey AWS_REMOTE_SERVICE = - AttributeKey.stringKey("aws.remote.service"); + static final AttributeKey AWS_REMOTE_SERVICE = stringKey("aws.remote.service"); - static final AttributeKey AWS_REMOTE_OPERATION = - AttributeKey.stringKey("aws.remote.operation"); + static final AttributeKey AWS_REMOTE_OPERATION = stringKey("aws.remote.operation"); - static final AttributeKey AWS_REMOTE_TARGET = AttributeKey.stringKey("aws.remote.target"); + static final AttributeKey AWS_REMOTE_TARGET = stringKey("aws.remote.target"); // use the same AWS Resource attribute name defined by OTel java auto-instr for aws_sdk_v_1_1 // TODO: all AWS specific attributes should be defined in semconv package and reused cross all // otel packages. Related sim - // https://github.com/open-telemetry/opentelemetry-java-instrumentation/issues/8710 - static final AttributeKey AWS_BUCKET_NAME = AttributeKey.stringKey("aws.bucket.name"); - static final AttributeKey AWS_QUEUE_NAME = AttributeKey.stringKey("aws.queue.name"); - static final AttributeKey AWS_STREAM_NAME = AttributeKey.stringKey("aws.stream.name"); - static final AttributeKey AWS_TABLE_NAME = AttributeKey.stringKey("aws.table.name"); + static final AttributeKey AWS_BUCKET_NAME = stringKey("aws.bucket.name"); + static final AttributeKey AWS_QUEUE_NAME = stringKey("aws.queue.name"); + static final AttributeKey AWS_STREAM_NAME = stringKey("aws.stream.name"); + static final AttributeKey AWS_TABLE_NAME = stringKey("aws.table.name"); } diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java index 42275b4b1..3fed3b3f3 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGenerator.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.awsxray; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_BUCKET_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_LOCAL_OPERATION; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_LOCAL_SERVICE; @@ -16,6 +18,7 @@ import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_STREAM_NAME; import static io.opentelemetry.contrib.awsxray.AwsAttributeKeys.AWS_TABLE_NAME; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; +import static java.util.logging.Level.FINEST; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -27,7 +30,6 @@ import java.net.MalformedURLException; import java.net.URL; import java.util.Optional; -import java.util.logging.Level; import java.util.logging.Logger; /** @@ -54,36 +56,31 @@ final class AwsMetricAttributeGenerator implements MetricAttributeGenerator { private static final String UNKNOWN_REMOTE_OPERATION = "UnknownRemoteOperation"; // copied from DbIncubatingAttributes - private static final AttributeKey DB_OPERATION = AttributeKey.stringKey("db.operation"); - private static final AttributeKey DB_SYSTEM = AttributeKey.stringKey("db.system"); + private static final AttributeKey DB_OPERATION = stringKey("db.operation"); + private static final AttributeKey DB_SYSTEM = stringKey("db.system"); // copied from FaasIncubatingAttributes - private static final AttributeKey FAAS_INVOKED_NAME = - AttributeKey.stringKey("faas.invoked_name"); - private static final AttributeKey FAAS_TRIGGER = AttributeKey.stringKey("faas.trigger"); + private static final AttributeKey FAAS_INVOKED_NAME = stringKey("faas.invoked_name"); + private static final AttributeKey FAAS_TRIGGER = stringKey("faas.trigger"); // copied from GraphqlIncubatingAttributes private static final AttributeKey GRAPHQL_OPERATION_TYPE = - AttributeKey.stringKey("graphql.operation.type"); + stringKey("graphql.operation.type"); // copied from HttpIncubatingAttributes - private static final AttributeKey HTTP_METHOD = AttributeKey.stringKey("http.method"); - private static final AttributeKey HTTP_TARGET = AttributeKey.stringKey("http.target"); - private static final AttributeKey HTTP_URL = AttributeKey.stringKey("http.url"); + private static final AttributeKey HTTP_METHOD = stringKey("http.method"); + private static final AttributeKey HTTP_TARGET = stringKey("http.target"); + private static final AttributeKey HTTP_URL = stringKey("http.url"); // copied from MessagingIncubatingAttributes - private static final AttributeKey MESSAGING_OPERATION = - AttributeKey.stringKey("messaging.operation"); - private static final AttributeKey MESSAGING_SYSTEM = - AttributeKey.stringKey("messaging.system"); + private static final AttributeKey MESSAGING_OPERATION = stringKey("messaging.operation"); + private static final AttributeKey MESSAGING_SYSTEM = stringKey("messaging.system"); // copied from NetIncubatingAttributes - private static final AttributeKey NET_PEER_NAME = AttributeKey.stringKey("net.peer.name"); - private static final AttributeKey NET_PEER_PORT = AttributeKey.longKey("net.peer.port"); - private static final AttributeKey NET_SOCK_PEER_ADDR = - AttributeKey.stringKey("net.sock.peer.addr"); - private static final AttributeKey NET_SOCK_PEER_PORT = - AttributeKey.longKey("net.sock.peer.port"); + private static final AttributeKey NET_PEER_NAME = stringKey("net.peer.name"); + private static final AttributeKey NET_PEER_PORT = longKey("net.peer.port"); + private static final AttributeKey NET_SOCK_PEER_ADDR = stringKey("net.sock.peer.addr"); + private static final AttributeKey NET_SOCK_PEER_PORT = longKey("net.sock.peer.port"); // copied from PeerIncubatingAttributes - private static final AttributeKey PEER_SERVICE = AttributeKey.stringKey("peer.service"); + private static final AttributeKey PEER_SERVICE = stringKey("peer.service"); // copied from RpcIncubatingAttributes - private static final AttributeKey RPC_METHOD = AttributeKey.stringKey("rpc.method"); - private static final AttributeKey RPC_SERVICE = AttributeKey.stringKey("rpc.service"); + private static final AttributeKey RPC_METHOD = stringKey("rpc.method"); + private static final AttributeKey RPC_SERVICE = stringKey("rpc.service"); @Override public Attributes generateMetricAttributesFromSpan(SpanData span, Resource resource) { @@ -307,7 +304,7 @@ private static String generateRemoteOperation(SpanData span) { remoteOperation = extractApiPathValue(url.getPath()); } } catch (MalformedURLException e) { - logger.log(Level.FINEST, "invalid http.url attribute: ", httpUrl); + logger.log(FINEST, "invalid http.url attribute: ", httpUrl); } } if (isKeyPresent(span, HTTP_METHOD)) { @@ -387,6 +384,6 @@ private static void logUnknownAttribute(AttributeKey attributeKey, SpanD String[] params = { attributeKey.getKey(), span.getKind().name(), span.getSpanContext().getSpanId() }; - logger.log(Level.FINEST, "No valid {0} value found for {1} span {2}", params); + logger.log(FINEST, "No valid {0} value found for {1} span {2}", params); } } diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java index 244138a47..9a3e60519 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.awsxray; +import static io.opentelemetry.api.common.AttributeKey.longKey; + import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.metrics.DoubleHistogram; @@ -40,8 +42,7 @@ @Immutable public final class AwsSpanMetricsProcessor implements SpanProcessor { - private static final AttributeKey HTTP_STATUS_CODE = - AttributeKey.longKey("http.status_code"); + private static final AttributeKey HTTP_STATUS_CODE = longKey("http.status_code"); private static final double NANOS_TO_MILLIS = 1_000_000.0; diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java index ad9b72a2c..9c997f042 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSampler.java @@ -5,6 +5,14 @@ package io.opentelemetry.contrib.awsxray; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.function.Function.identity; +import static java.util.logging.Level.FINE; +import static java.util.stream.Collectors.toList; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toSet; + import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; @@ -29,17 +37,13 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.annotation.Nullable; /** Remote sampler that gets sampling configuration from AWS X-Ray. */ public final class AwsXrayRemoteSampler implements Sampler, Closeable { - static final long DEFAULT_TARGET_INTERVAL_NANOS = TimeUnit.SECONDS.toNanos(10); + static final long DEFAULT_TARGET_INTERVAL_NANOS = SECONDS.toNanos(10); private static final Logger logger = Logger.getLogger(AwsXrayRemoteSampler.class.getName()); @@ -134,7 +138,7 @@ private void getAndUpdateSampler() { initialSampler, response.getSamplingRules().stream() .map(SamplingRuleRecord::getRule) - .collect(Collectors.toList()))); + .collect(toList()))); previousRulesResponse = response; ScheduledFuture existingFetchTargetsFuture = fetchTargetsFuture; @@ -142,18 +146,17 @@ private void getAndUpdateSampler() { existingFetchTargetsFuture.cancel(false); } fetchTargetsFuture = - executor.schedule( - this::fetchTargets, DEFAULT_TARGET_INTERVAL_NANOS, TimeUnit.NANOSECONDS); + executor.schedule(this::fetchTargets, DEFAULT_TARGET_INTERVAL_NANOS, NANOSECONDS); } } catch (Throwable t) { - logger.log(Level.FINE, "Failed to update sampler", t); + logger.log(FINE, "Failed to update sampler", t); } scheduleSamplerUpdate(); } private void scheduleSamplerUpdate() { long delay = pollingIntervalNanos + jitterNanos.next(); - pollFuture = executor.schedule(this::getAndUpdateSampler, delay, TimeUnit.NANOSECONDS); + pollFuture = executor.schedule(this::getAndUpdateSampler, delay, NANOSECONDS); } /** @@ -168,7 +171,7 @@ Duration getNextSamplerUpdateScheduledDuration() { if (pollFuture == null) { return null; } - return Duration.ofNanos(pollFuture.getDelay(TimeUnit.NANOSECONDS)); + return Duration.ofNanos(pollFuture.getDelay(NANOSECONDS)); } private void fetchTargets() { @@ -181,28 +184,25 @@ private void fetchTargets() { Date now = Date.from(Instant.ofEpochSecond(0, clock.now())); List statistics = xrayRulesSampler.snapshot(now); Set requestedTargetRuleNames = - statistics.stream() - .map(SamplingStatisticsDocument::getRuleName) - .collect(Collectors.toSet()); + statistics.stream().map(SamplingStatisticsDocument::getRuleName).collect(toSet()); GetSamplingTargetsResponse response = client.getSamplingTargets(GetSamplingTargetsRequest.create(statistics)); Map targets = response.getDocuments().stream() - .collect(Collectors.toMap(SamplingTargetDocument::getRuleName, Function.identity())); + .collect(toMap(SamplingTargetDocument::getRuleName, identity())); updateInternalSamplers(xrayRulesSampler.withTargets(targets, requestedTargetRuleNames, now)); } catch (Throwable t) { // Might be a transient API failure, try again after a default interval. fetchTargetsFuture = - executor.schedule( - this::fetchTargets, DEFAULT_TARGET_INTERVAL_NANOS, TimeUnit.NANOSECONDS); + executor.schedule(this::fetchTargets, DEFAULT_TARGET_INTERVAL_NANOS, NANOSECONDS); return; } long nextTargetFetchIntervalNanos = xrayRulesSampler.nextTargetFetchTimeNanos() - clock.nanoTime(); fetchTargetsFuture = - executor.schedule(this::fetchTargets, nextTargetFetchIntervalNanos, TimeUnit.NANOSECONDS); + executor.schedule(this::fetchTargets, nextTargetFetchIntervalNanos, NANOSECONDS); } @Override diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerBuilder.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerBuilder.java index 1ce0d41c1..25485e4b0 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerBuilder.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsXrayRemoteSamplerBuilder.java @@ -6,6 +6,8 @@ package io.opentelemetry.contrib.awsxray; import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.NANOSECONDS; +import static java.util.concurrent.TimeUnit.SECONDS; import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.sdk.common.Clock; @@ -26,7 +28,7 @@ public final class AwsXrayRemoteSamplerBuilder { private Clock clock = Clock.getDefault(); private String endpoint = DEFAULT_ENDPOINT; @Nullable private Sampler initialSampler; - private long pollingIntervalNanos = TimeUnit.SECONDS.toNanos(DEFAULT_POLLING_INTERVAL_SECS); + private long pollingIntervalNanos = SECONDS.toNanos(DEFAULT_POLLING_INTERVAL_SECS); AwsXrayRemoteSamplerBuilder(Resource resource) { this.resource = resource; @@ -51,7 +53,7 @@ public AwsXrayRemoteSamplerBuilder setEndpoint(String endpoint) { @CanIgnoreReturnValue public AwsXrayRemoteSamplerBuilder setPollingInterval(Duration delay) { requireNonNull(delay, "delay"); - return setPollingInterval(delay.toNanos(), TimeUnit.NANOSECONDS); + return setPollingInterval(delay.toNanos(), NANOSECONDS); } /** diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java index 1d97c4aed..ae4cac018 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/SamplingRuleApplier.java @@ -5,7 +5,10 @@ package io.opentelemetry.contrib.awsxray; +import static io.opentelemetry.api.common.AttributeKey.stringKey; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; +import static java.util.concurrent.TimeUnit.SECONDS; +import static java.util.stream.Collectors.toMap; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -28,22 +31,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.LongAdder; import java.util.regex.Pattern; -import java.util.stream.Collectors; import javax.annotation.Nullable; final class SamplingRuleApplier { // copied from AwsIncubatingAttributes private static final AttributeKey AWS_ECS_CONTAINER_ARN = - AttributeKey.stringKey("aws.ecs.container.arn"); + stringKey("aws.ecs.container.arn"); // copied from CloudIncubatingAttributes - private static final AttributeKey CLOUD_PLATFORM = - AttributeKey.stringKey("cloud.platform"); - private static final AttributeKey CLOUD_RESOURCE_ID = - AttributeKey.stringKey("cloud.resource_id"); + private static final AttributeKey CLOUD_PLATFORM = stringKey("cloud.platform"); + private static final AttributeKey CLOUD_RESOURCE_ID = stringKey("cloud.resource_id"); // copied from CloudIncubatingAttributes.CloudPlatformIncubatingValues public static final String AWS_EC2 = "aws_ec2"; public static final String AWS_ECS = "aws_ecs"; @@ -51,12 +50,12 @@ final class SamplingRuleApplier { public static final String AWS_LAMBDA = "aws_lambda"; public static final String AWS_ELASTIC_BEANSTALK = "aws_elastic_beanstalk"; // copied from HttpIncubatingAttributes - private static final AttributeKey HTTP_HOST = AttributeKey.stringKey("http.host"); - private static final AttributeKey HTTP_METHOD = AttributeKey.stringKey("http.method"); - private static final AttributeKey HTTP_TARGET = AttributeKey.stringKey("http.target"); - private static final AttributeKey HTTP_URL = AttributeKey.stringKey("http.url"); + private static final AttributeKey HTTP_HOST = stringKey("http.host"); + private static final AttributeKey HTTP_METHOD = stringKey("http.method"); + private static final AttributeKey HTTP_TARGET = stringKey("http.target"); + private static final AttributeKey HTTP_URL = stringKey("http.url"); // copied from NetIncubatingAttributes - private static final AttributeKey NET_HOST_NAME = AttributeKey.stringKey("net.host.name"); + private static final AttributeKey NET_HOST_NAME = stringKey("net.host.name"); private static final Map XRAY_CLOUD_PLATFORM; @@ -131,7 +130,7 @@ final class SamplingRuleApplier { } else { attributeMatchers = rule.getAttributes().entrySet().stream() - .collect(Collectors.toMap(Map.Entry::getKey, e -> toMatcher(e.getValue()))); + .collect(toMap(Map.Entry::getKey, e -> toMatcher(e.getValue()))); } urlPathMatcher = toMatcher(rule.getUrlPath()); @@ -317,7 +316,7 @@ SamplingRuleApplier withTarget(SamplingTargetDocument target, Date now) { } long intervalNanos = target.getIntervalSecs() != null - ? TimeUnit.SECONDS.toNanos(target.getIntervalSecs()) + ? SECONDS.toNanos(target.getIntervalSecs()) : AwsXrayRemoteSampler.DEFAULT_TARGET_INTERVAL_NANOS; long newNextSnapshotTimeNanos = clock.nanoTime() + intervalNanos; diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java index 75977dc0f..e187da972 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XrayRulesSampler.java @@ -5,6 +5,9 @@ package io.opentelemetry.contrib.awsxray; +import static java.util.logging.Level.FINE; +import static java.util.stream.Collectors.toList; + import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; @@ -21,9 +24,7 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; final class XrayRulesSampler implements Sampler { @@ -84,7 +85,7 @@ public SamplingResult shouldSample( // In practice, X-Ray always returns a Default rule that matches all requests so it is a bug in // our code or X-Ray to reach here, fallback just in case. logger.log( - Level.FINE, + FINE, "No sampling rule matched the request. " + "This is a bug in either the OpenTelemetry SDK or X-Ray."); return fallbackSampler.shouldSample( @@ -100,7 +101,7 @@ List snapshot(Date now) { return Arrays.stream(ruleAppliers) .map(rule -> rule.snapshot(now)) .filter(Objects::nonNull) - .collect(Collectors.toList()); + .collect(toList()); } long nextTargetFetchTimeNanos() { diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java index 5dbbbbbbf..1c7958889 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java @@ -25,6 +25,8 @@ package io.opentelemetry.contrib.awsxray; +import static java.util.logging.Level.FINE; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; @@ -37,7 +39,6 @@ import java.io.UncheckedIOException; import java.math.BigDecimal; import java.util.Date; -import java.util.logging.Level; import java.util.logging.Logger; import okhttp3.Call; import okhttp3.MediaType; @@ -114,7 +115,7 @@ private T executeJsonRequest(String endpoint, Object request, Class respo private static String readResponse(Response response, String endpoint) throws IOException { if (!response.isSuccessful()) { logger.log( - Level.FINE, + FINE, "Error response from " + endpoint + " code (" diff --git a/azure-resources/build.gradle.kts b/azure-resources/build.gradle.kts index c17d14787..43a154970 100644 --- a/azure-resources/build.gradle.kts +++ b/azure-resources/build.gradle.kts @@ -5,8 +5,8 @@ plugins { id("maven-publish") } -description = "OpenTelemetry GCP Resources Support" -otelJava.moduleName.set("io.opentelemetry.contrib.gcp.resource") +description = "OpenTelemetry Azure Resources Support" +otelJava.moduleName.set("io.opentelemetry.contrib.azure.resource") // enable publishing to maven local java { diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java index 74ad96334..485ef7b01 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java @@ -12,6 +12,7 @@ import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.HOST_ID; import static io.opentelemetry.contrib.azure.resource.IncubatingAttributes.SERVICE_INSTANCE_ID; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; +import static java.util.Objects.requireNonNull; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -20,7 +21,6 @@ import io.opentelemetry.sdk.resources.Resource; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import javax.annotation.Nullable; public class AzureAppServiceResourceProvider extends CloudResourceProvider { @@ -68,7 +68,7 @@ public Attributes getAttributes() { if (detect != AzureEnvVarPlatform.APP_SERVICE) { return Attributes.empty(); } - String name = Objects.requireNonNull(env.get(WEBSITE_SITE_NAME)); + String name = requireNonNull(env.get(WEBSITE_SITE_NAME)); AttributesBuilder builder = AzureVmResourceProvider.azureAttributeBuilder(AZURE_APP_SERVICE); builder.put(SERVICE_NAME, name); diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java index d5bf44520..ced58f0d2 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java @@ -5,12 +5,13 @@ package io.opentelemetry.contrib.azure.resource; +import static java.util.Objects.requireNonNull; + import com.fasterxml.jackson.core.JsonFactory; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; -import java.util.Objects; import java.util.Optional; import java.util.function.Supplier; import java.util.logging.Level; @@ -22,6 +23,8 @@ public class AzureMetadataService { static final JsonFactory JSON_FACTORY = new JsonFactory(); private static final URL METADATA_URL; + private static final Duration TIMEOUT = Duration.ofSeconds(5); + private static final Logger logger = Logger.getLogger(AzureMetadataService.class.getName()); static { try { @@ -31,12 +34,6 @@ public class AzureMetadataService { } } - private AzureMetadataService() {} - - private static final Duration TIMEOUT = Duration.ofSeconds(5); - - private static final Logger logger = Logger.getLogger(AzureMetadataService.class.getName()); - static Supplier> defaultClient() { return () -> fetchMetadata(METADATA_URL); } @@ -66,10 +63,12 @@ static Optional fetchMetadata(URL url) { return Optional.empty(); } - return Optional.of(Objects.requireNonNull(response.body()).string()); + return Optional.of(requireNonNull(response.body()).string()); } catch (IOException e) { logger.log(Level.FINE, "Failed to fetch Azure VM metadata", e); return Optional.empty(); } } + + private AzureMetadataService() {} } diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java index 38c983b4a..f36590622 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java @@ -36,20 +36,6 @@ public class AzureVmResourceProvider extends CloudResourceProvider { - static class Entry { - final AttributeKey key; - final Function transform; - - Entry(AttributeKey key) { - this(key, Function.identity()); - } - - Entry(AttributeKey key, Function transform) { - this.key = key; - this.transform = transform; - } - } - private static final Map COMPUTE_MAPPING = new HashMap<>(); static { @@ -161,4 +147,18 @@ private static void consumeJson(JsonParser parser, BiConsumer co consumer.accept(parser.currentName(), parser.nextTextValue()); } } + + static class Entry { + final AttributeKey key; + final Function transform; + + Entry(AttributeKey key) { + this(key, Function.identity()); + } + + Entry(AttributeKey key, Function transform) { + this.key = key; + this.transform = transform; + } + } } diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java index 20d856cba..75360f0e9 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.azure.resource; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PLATFORM; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PROVIDER; @@ -16,7 +17,6 @@ import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -98,7 +98,7 @@ void isFunction() { @NotNull private static AttributesAssert createResource(Map map) { - return OpenTelemetryAssertions.assertThat( + return assertThat( new AzureAppServiceResourceProvider(map).createResource(null).getAttributes()); } } diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProviderTest.java index 5ac1a4be7..082ceaf6a 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProviderTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.azure.resource; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PLATFORM; @@ -13,7 +14,6 @@ import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -50,7 +50,7 @@ void isNotContainer() { @NotNull private static AttributesAssert createResource(Map map) { - return OpenTelemetryAssertions.assertThat( + return assertThat( new AzureContainersResourceProvider(map).createResource(null).getAttributes()); } } diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java index 520e44543..e2c39cc7a 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.azure.resource; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PLATFORM; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PROVIDER; import static io.opentelemetry.semconv.incubating.FaasIncubatingAttributes.FAAS_INSTANCE; @@ -14,7 +15,6 @@ import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; import java.util.HashMap; import java.util.Map; import org.jetbrains.annotations.NotNull; @@ -55,7 +55,6 @@ void isNotFunction() { @NotNull private static AttributesAssert createResource(Map map) { - return OpenTelemetryAssertions.assertThat( - new AzureFunctionsResourceProvider(map).createResource(null).getAttributes()); + return assertThat(new AzureFunctionsResourceProvider(map).createResource(null).getAttributes()); } } diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java index d827e8fd4..25d9e065c 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.azure.resource; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PLATFORM; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_PROVIDER; @@ -16,7 +17,6 @@ import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.assertj.AttributesAssert; -import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; import java.io.IOException; import java.io.InputStreamReader; import java.net.MalformedURLException; @@ -47,7 +47,7 @@ private AttributesAssert mockServerResponse() { @NotNull private AttributesAssert createResource(Supplier> client) { Resource resource = getResourceProvider(client).createResource(null); - return OpenTelemetryAssertions.assertThat(resource.getAttributes()); + return assertThat(resource.getAttributes()); } @NotNull diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java index 4e8c91505..b3701bf52 100644 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java @@ -18,14 +18,6 @@ */ public class BaggageLogRecordProcessor implements LogRecordProcessor { - /** - * Creates a new {@link BaggageLogRecordProcessor} that copies all baggage entries into the newly - * created log record. - */ - public static BaggageLogRecordProcessor allowAllBaggageKeys() { - return new BaggageLogRecordProcessor(baggageKey -> true); - } - private final Predicate baggageKeyPredicate; /** @@ -36,6 +28,14 @@ public BaggageLogRecordProcessor(Predicate baggageKeyPredicate) { this.baggageKeyPredicate = baggageKeyPredicate; } + /** + * Creates a new {@link BaggageLogRecordProcessor} that copies all baggage entries into the newly + * created log record. + */ + public static BaggageLogRecordProcessor allowAllBaggageKeys() { + return new BaggageLogRecordProcessor(baggageKey -> true); + } + @Override public void onEmit(Context context, ReadWriteLogRecord logRecord) { Baggage.fromContext(context) diff --git a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResource.java b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResource.java index 7d6313928..c8e7bd2f2 100644 --- a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResource.java +++ b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResource.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.cloudfoundry.resources; +import static io.opentelemetry.api.common.AttributeKey.stringKey; + import com.fasterxml.jackson.core.JsonFactory; import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonToken; @@ -22,24 +24,22 @@ public final class CloudFoundryResource { private static final String ENV_VCAP_APPLICATION = "VCAP_APPLICATION"; // copied from CloudfoundryIncubatingAttributes - private static final AttributeKey CLOUDFOUNDRY_APP_ID = - AttributeKey.stringKey("cloudfoundry.app.id"); + private static final AttributeKey CLOUDFOUNDRY_APP_ID = stringKey("cloudfoundry.app.id"); private static final AttributeKey CLOUDFOUNDRY_APP_INSTANCE_ID = - AttributeKey.stringKey("cloudfoundry.app.instance.id"); + stringKey("cloudfoundry.app.instance.id"); private static final AttributeKey CLOUDFOUNDRY_APP_NAME = - AttributeKey.stringKey("cloudfoundry.app.name"); - private static final AttributeKey CLOUDFOUNDRY_ORG_ID = - AttributeKey.stringKey("cloudfoundry.org.id"); + stringKey("cloudfoundry.app.name"); + private static final AttributeKey CLOUDFOUNDRY_ORG_ID = stringKey("cloudfoundry.org.id"); private static final AttributeKey CLOUDFOUNDRY_ORG_NAME = - AttributeKey.stringKey("cloudfoundry.org.name"); + stringKey("cloudfoundry.org.name"); private static final AttributeKey CLOUDFOUNDRY_PROCESS_ID = - AttributeKey.stringKey("cloudfoundry.process.id"); + stringKey("cloudfoundry.process.id"); private static final AttributeKey CLOUDFOUNDRY_PROCESS_TYPE = - AttributeKey.stringKey("cloudfoundry.process.type"); + stringKey("cloudfoundry.process.type"); private static final AttributeKey CLOUDFOUNDRY_SPACE_ID = - AttributeKey.stringKey("cloudfoundry.space.id"); + stringKey("cloudfoundry.space.id"); private static final AttributeKey CLOUDFOUNDRY_SPACE_NAME = - AttributeKey.stringKey("cloudfoundry.space.name"); + stringKey("cloudfoundry.space.name"); private static final Logger LOG = Logger.getLogger(CloudFoundryResource.class.getName()); private static final JsonFactory JSON_FACTORY = new JsonFactory(); private static final Resource INSTANCE = buildResource(System::getenv); diff --git a/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceTest.java b/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceTest.java index 1c533cd8a..96474c966 100644 --- a/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceTest.java +++ b/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceTest.java @@ -5,7 +5,9 @@ package io.opentelemetry.contrib.cloudfoundry.resources; +import static java.util.stream.Collectors.joining; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.semconv.SchemaUrls; @@ -18,8 +20,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.stream.Collectors; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; class CloudFoundryResourceTest { @@ -36,11 +36,11 @@ private static String loadVcapApplicationSample(String filename) { if (is != null) { return new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)) .lines() - .collect(Collectors.joining()); + .collect(joining()); } - Assertions.fail("Cannot load resource " + filename); + fail("Cannot load resource " + filename); } catch (IOException e) { - Assertions.fail("Error reading " + filename); + fail("Error reading " + filename); } return ""; } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java index 4853ee72f..e8a7f0bd0 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java @@ -5,9 +5,11 @@ package io.opentelemetry.contrib.disk.buffering.config; +import static java.util.concurrent.TimeUnit.HOURS; +import static java.util.concurrent.TimeUnit.SECONDS; + import com.google.auto.value.AutoValue; import java.io.File; -import java.util.concurrent.TimeUnit; /** Defines how the storage should be managed. */ @AutoValue @@ -56,9 +58,9 @@ public static Builder builder() { return new AutoValue_StorageConfiguration.Builder() .setMaxFileSize(1024 * 1024) // 1MB .setMaxFolderSize(10 * 1024 * 1024) // 10MB - .setMaxFileAgeForWriteMillis(TimeUnit.SECONDS.toMillis(30)) - .setMinFileAgeForReadMillis(TimeUnit.SECONDS.toMillis(33)) - .setMaxFileAgeForReadMillis(TimeUnit.HOURS.toMillis(18)) + .setMaxFileAgeForWriteMillis(SECONDS.toMillis(30)) + .setMinFileAgeForReadMillis(SECONDS.toMillis(33)) + .setMaxFileAgeForReadMillis(HOURS.toMillis(18)) .setDebugEnabled(false); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java index 51d135299..c508d042f 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.disk.buffering.exporters; +import static java.util.concurrent.TimeUnit.MILLISECONDS; + import io.opentelemetry.contrib.disk.buffering.SignalType; import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; @@ -13,7 +15,6 @@ import java.util.Collection; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; /** Internal utility for common export to disk operations across all exporters. */ @@ -34,7 +35,7 @@ public SignalStorageExporter( public CompletableResultCode exportToStorage(Collection items) { CompletableFuture future = storage.write(items); try { - WriteResult operation = future.get(writeTimeout.toMillis(), TimeUnit.MILLISECONDS); + WriteResult operation = future.get(writeTimeout.toMillis(), MILLISECONDS); if (operation.isSuccessful()) { callback.onExportSuccess(type); return CompletableResultCode.ofSuccess(); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java index 2bda19da9..3efe5c367 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java @@ -16,9 +16,10 @@ /** Exporter that stores spans into disk. */ public final class SpanToDiskExporter implements SpanExporter { + private static final SignalType TYPE = SignalType.SPAN; + private final SignalStorageExporter storageExporter; private final ExporterCallback callback; - private static final SignalType TYPE = SignalType.SPAN; private SpanToDiskExporter( SignalStorageExporter storageExporter, ExporterCallback callback) { @@ -26,7 +27,7 @@ private SpanToDiskExporter( this.callback = callback; } - public Builder builder(SignalStorage.Span storage) { + public static Builder builder(SignalStorage.Span storage) { return new Builder(storage); } @@ -51,6 +52,10 @@ public static final class Builder { private ExporterCallback callback = ExporterCallback.noop(); private Duration writeTimeout = Duration.ofSeconds(10); + private Builder(SignalStorage.Span storage) { + this.storage = storage; + } + @CanIgnoreReturnValue public Builder setExporterCallback(ExporterCallback value) { callback = value; @@ -68,9 +73,5 @@ public SpanToDiskExporter build() { new SignalStorageExporter<>(storage, callback, writeTimeout, TYPE); return new SpanToDiskExporter(storageExporter, callback); } - - private Builder(SignalStorage.Span storage) { - this.storage = storage; - } } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java index 5b2dcd186..a3104a892 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java @@ -5,6 +5,9 @@ package io.opentelemetry.contrib.disk.buffering.internal.exporter; +import static java.util.logging.Level.FINER; +import static java.util.logging.Level.WARNING; + import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; import io.opentelemetry.contrib.disk.buffering.internal.utils.DebugLogger; @@ -12,7 +15,6 @@ import java.io.IOException; import java.util.Collection; import java.util.function.Function; -import java.util.logging.Level; import java.util.logging.Logger; public class ToDiskExporter { @@ -39,7 +41,7 @@ public static ToDiskExporterBuilder builder(Storage storage) { } public synchronized CompletableResultCode export(Collection data) { - logger.log("Intercepting exporter batch.", Level.FINER); + logger.log("Intercepting exporter batch.", FINER); try { serializer.initialize(data); if (storage.write(serializer)) { @@ -50,7 +52,7 @@ public synchronized CompletableResultCode export(Collection data) { } catch (IOException e) { logger.log( "An unexpected error happened while attempting to write the data in disk. Exporting it right away.", - Level.WARNING, + WARNING, e); return exportFunction.apply(data); } finally { diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java index d43bc18b2..ebea37171 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java @@ -5,13 +5,14 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage; +import static java.util.logging.Level.INFO; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.sdk.common.Clock; import java.io.File; import java.io.IOException; -import java.util.logging.Level; import java.util.logging.Logger; public class StorageBuilder { @@ -43,7 +44,7 @@ public Storage build() throws IOException { File folder = ensureSubdir(configuration.getRootDir(), folderName); FolderManager folderManager = new FolderManager(folder, configuration, clock); if (configuration.isDebugEnabled()) { - logger.log(Level.INFO, "Building storage with configuration => " + configuration); + logger.log(INFO, "Building storage with configuration => " + configuration); } return new Storage(folderManager, configuration.isDebugEnabled()); } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java index 46ff72ebf..b0bb67624 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.disk.buffering.internal.utils; +import static java.util.logging.Level.INFO; + import java.util.logging.Level; import java.util.logging.Logger; @@ -22,7 +24,7 @@ public static DebugLogger wrap(Logger logger, boolean debugEnabled) { } public void log(String msg) { - log(msg, Level.INFO); + log(msg, INFO); } public void log(String msg, Level level) { 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..aba0ddf11 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 @@ -5,9 +5,10 @@ package io.opentelemetry.contrib.gcp.auth; +import static java.util.Locale.ROOT; + 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; @@ -60,8 +61,7 @@ enum ConfigurableOption { ConfigurableOption(String userReadableName) { this.userReadableName = userReadableName; this.environmentVariableName = this.name(); - this.systemPropertyName = - this.environmentVariableName.toLowerCase(Locale.ROOT).replace('_', '.'); + this.systemPropertyName = this.environmentVariableName.toLowerCase(ROOT).replace('_', '.'); } /** 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..12f73d5bb 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 @@ -5,9 +5,13 @@ package io.opentelemetry.contrib.gcp.auth; +import static io.opentelemetry.api.common.AttributeKey.stringKey; +import static java.util.Arrays.stream; +import static java.util.stream.Collectors.joining; +import static java.util.stream.Collectors.toMap; + import com.google.auth.oauth2.GoogleCredentials; import com.google.auto.service.AutoService; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason; import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; @@ -25,14 +29,12 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; -import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.annotation.Nonnull; /** @@ -150,7 +152,7 @@ private static boolean isSignalTargeted(String checkSignal, ConfigProperties con String userSpecifiedTargetedSignals = ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback( configProperties, () -> SIGNAL_TYPE_ALL); - return Arrays.stream(userSpecifiedTargetedSignals.split(",")) + return stream(userSpecifiedTargetedSignals.split(",")) .map(String::trim) .anyMatch( targetedSignal -> @@ -202,17 +204,16 @@ private static Map getRequiredHeaderMap( } catch (IOException e) { throw new GoogleAuthException(Reason.FAILED_ADC_REFRESH, e); } - // flatten list Map flattenedHeaders = gcpHeaders.entrySet().stream() .collect( - Collectors.toMap( + toMap( Map.Entry::getKey, entry -> entry.getValue().stream() .filter(Objects::nonNull) // Filter nulls .filter(s -> !s.isEmpty()) // Filter empty strings - .collect(Collectors.joining(",")))); + .collect(joining(",")))); // 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)) { @@ -230,9 +231,7 @@ private static Map getRequiredHeaderMap( 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)); + Resource res = Resource.create(Attributes.of(stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId)); return resource.merge(res); } } diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 4fb687925..be0dbe7b2 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -14,6 +14,9 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; @@ -135,15 +138,15 @@ public void testTraceCustomizerOtlpHttp() { ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_TRACES); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); - OtlpHttpSpanExporter mockOtlpHttpSpanExporter = Mockito.mock(OtlpHttpSpanExporter.class); + OtlpHttpSpanExporter mockOtlpHttpSpanExporter = mock(OtlpHttpSpanExporter.class); OtlpHttpSpanExporterBuilder otlpSpanExporterBuilder = OtlpHttpSpanExporter.builder(); OtlpHttpSpanExporterBuilder spyOtlpHttpSpanExporterBuilder = Mockito.spy(otlpSpanExporterBuilder); - Mockito.when(spyOtlpHttpSpanExporterBuilder.build()).thenReturn(mockOtlpHttpSpanExporter); + when(spyOtlpHttpSpanExporterBuilder.build()).thenReturn(mockOtlpHttpSpanExporter); - Mockito.when(mockOtlpHttpSpanExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); + when(mockOtlpHttpSpanExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); List exportedSpans = new ArrayList<>(); - Mockito.when(mockOtlpHttpSpanExporter.export(Mockito.anyCollection())) + when(mockOtlpHttpSpanExporter.export(any())) .thenAnswer( invocationOnMock -> { exportedSpans.addAll(invocationOnMock.getArgument(0)); diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java index b7e49c1a9..4b92ade57 100644 --- a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java @@ -65,6 +65,7 @@ public class GCPResourceProvider implements ConditionalResourceProvider { private static final Logger LOGGER = Logger.getLogger(GCPResourceProvider.class.getSimpleName()); + private final GCPPlatformDetector detector; // for testing only diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/IncubatingAttributes.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/IncubatingAttributes.java index 745d440fd..2f0f937f0 100644 --- a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/IncubatingAttributes.java +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/IncubatingAttributes.java @@ -13,8 +13,6 @@ */ class IncubatingAttributes { - private IncubatingAttributes() {} - public static final AttributeKey CLOUD_ACCOUNT_ID = AttributeKey.stringKey("cloud.account.id"); public static final AttributeKey CLOUD_AVAILABILITY_ZONE = @@ -26,7 +24,6 @@ private IncubatingAttributes() {} public static final AttributeKey CLOUD_REGION = AttributeKey.stringKey("cloud.region"); public static final class CloudPlatformIncubatingValues { - private CloudPlatformIncubatingValues() {} public static final String GCP_COMPUTE_ENGINE = "gcp_compute_engine"; public static final String GCP_CLOUD_RUN = "gcp_cloud_run"; @@ -34,6 +31,8 @@ private CloudPlatformIncubatingValues() {} public static final String GCP_CLOUD_FUNCTIONS = "gcp_cloud_functions"; public static final String GCP_APP_ENGINE = "gcp_app_engine"; public static final String GCP = "gcp"; + + private CloudPlatformIncubatingValues() {} } public static final AttributeKey FAAS_INSTANCE = AttributeKey.stringKey("faas.instance"); @@ -56,4 +55,6 @@ private CloudPlatformIncubatingValues() {} public static final AttributeKey K8S_CLUSTER_NAME = AttributeKey.stringKey("k8s.cluster.name"); + + private IncubatingAttributes() {} } diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java index 25d173d3d..e6ddfae17 100644 --- a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/MetricAssert.java @@ -5,9 +5,10 @@ package io.opentelemetry.ibm.mq.metricscollector; +import static org.assertj.core.api.Assertions.assertThat; + import io.opentelemetry.sdk.metrics.data.LongPointData; import io.opentelemetry.sdk.metrics.data.MetricData; -import org.assertj.core.api.Assertions; public class MetricAssert { @@ -24,12 +25,12 @@ static MetricAssert assertThatMetric(MetricData metric, int pointOffset) { } MetricAssert hasName(String name) { - Assertions.assertThat(metric.getName()).isEqualTo(name); + assertThat(metric.getName()).isEqualTo(name); return this; } MetricAssert hasValue(long value) { - Assertions.assertThat( + assertThat( ((LongPointData) metric.getLongGaugeData().getPoints().toArray()[this.pointOffset]) .getValue()) .isEqualTo(value); diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java index 9c8118ec5..7ee78943e 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfig.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.inferredspans; +import static java.util.stream.Collectors.toList; + import com.google.auto.service.AutoService; import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; @@ -17,7 +19,6 @@ import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.logging.Logger; -import java.util.stream.Collectors; import javax.annotation.Nullable; @AutoService(AutoConfigurationCustomizerProvider.class) @@ -117,7 +118,7 @@ void applyWildcards(String configKey, Consumer> fu Arrays.stream(wildcardListString.split(",")) .filter(str -> !str.isEmpty()) .map(WildcardMatcher::valueOf) - .collect(Collectors.toList()); + .collect(toList()); if (!values.isEmpty()) { funcToApply.accept(values); } diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java index 22f59ba53..a9288bac2 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.inferredspans; +import static java.util.Objects.requireNonNull; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.api.trace.TracerProvider; @@ -19,7 +21,6 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.util.Objects; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -33,14 +34,12 @@ public class InferredSpansProcessor implements SpanProcessor { private static final Logger logger = Logger.getLogger(InferredSpansProcessor.class.getName()); public static final String TRACER_NAME = "inferred-spans"; - public static final String TRACER_VERSION = readInferredSpansVersion(); // Visible for testing final SamplingProfiler profiler; private Supplier tracerProvider = GlobalOpenTelemetry::getTracerProvider; - @Nullable private volatile Tracer tracer; InferredSpansProcessor( @@ -134,7 +133,7 @@ private static String readInferredSpansVersion() { Properties properties = new Properties(); properties.load(is); String version = (String) properties.get("contrib.version"); - Objects.requireNonNull(version); + requireNonNull(version); return version; } catch (IOException e) { throw new IllegalStateException(e); diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java index b464f0f42..3124f3341 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.inferredspans; +import static java.util.Arrays.asList; + import io.opentelemetry.api.trace.SpanBuilder; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.contrib.inferredspans.internal.CallTree; @@ -12,7 +14,6 @@ import io.opentelemetry.contrib.inferredspans.internal.SpanAnchoredClock; import java.io.File; import java.time.Duration; -import java.util.Arrays; import java.util.List; import java.util.function.BiConsumer; import javax.annotation.Nullable; @@ -27,7 +28,7 @@ public class InferredSpansProcessorBuilder { private Duration inferredSpansMinDuration = Duration.ZERO; private List includedClasses = WildcardMatcher.matchAllList(); private List excludedClasses = - Arrays.asList( + asList( WildcardMatcher.caseSensitiveMatcher("java.*"), WildcardMatcher.caseSensitiveMatcher("javax.*"), WildcardMatcher.caseSensitiveMatcher("sun.*"), @@ -43,13 +44,11 @@ public class InferredSpansProcessorBuilder { WildcardMatcher.caseSensitiveMatcher("io.undertow.*")); private Duration profilerInterval = Duration.ofSeconds(5); private Duration profilingDuration = Duration.ofSeconds(5); - @Nullable private String profilerLibDirectory = null; // The following options are only intended to be modified in tests private SpanAnchoredClock clock = new SpanAnchoredClock(); private boolean startScheduledProfiling = true; - @Nullable private File activationEventsFile = null; @Nullable private File jfrFile = null; private BiConsumer parentOverrideHandler = diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/WildcardMatcher.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/WildcardMatcher.java index b6b788c55..7e82a0ec1 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/WildcardMatcher.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/WildcardMatcher.java @@ -5,8 +5,9 @@ package io.opentelemetry.contrib.inferredspans; +import static java.util.Collections.singletonList; + import java.util.ArrayList; -import java.util.Collections; import java.util.List; import javax.annotation.Nullable; @@ -32,7 +33,7 @@ public abstract class WildcardMatcher { private static final String CASE_SENSITIVE_PREFIX = "(?-i)"; private static final String WILDCARD = "*"; private static final WildcardMatcher MATCH_ALL = valueOf(WILDCARD); - private static final List MATCH_ALL_LIST = Collections.singletonList(MATCH_ALL); + private static final List MATCH_ALL_LIST = singletonList(MATCH_ALL); public static WildcardMatcher caseSensitiveMatcher(String matcher) { return valueOf(CASE_SENSITIVE_PREFIX + matcher); diff --git a/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java b/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java index 939a60af6..7225e72fa 100644 --- a/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java +++ b/jfr-events/src/test/java/io/opentelemetry/contrib/jfrevent/JfrSpanProcessorTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.jfrevent; -import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; @@ -52,7 +52,7 @@ void tearDown() { * @throws java.io.IOException on io error */ @Test - public void basicSpan() throws IOException { + void basicSpan() throws IOException { Path output = Files.createTempFile("test-basic-span", ".jfr"); try { @@ -89,7 +89,7 @@ public void basicSpan() throws IOException { * @throws java.lang.InterruptedException interrupted sleep */ @Test - public void basicSpanWithScope() throws IOException, InterruptedException { + void basicSpanWithScope() throws IOException, InterruptedException { Path output = Files.createTempFile("test-basic-span-with-scope", ".jfr"); try { diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java index ceba4b2fe..b3c432db6 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.jmxscraper; +import static java.util.logging.Level.WARNING; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.io.IOException; import java.net.MalformedURLException; @@ -17,7 +19,6 @@ import java.util.HashMap; import java.util.Locale; import java.util.Map; -import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; import javax.management.remote.JMXConnector; @@ -146,7 +147,7 @@ private Map buildEnv() { } }); } catch (ReflectiveOperationException e) { - logger.log(Level.WARNING, "SASL unsupported in current environment: " + e.getMessage()); + logger.log(WARNING, "SASL unsupported in current environment: " + e.getMessage()); } return env; } diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index 85c47ba0e..a5e0d4de1 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -5,6 +5,12 @@ package io.opentelemetry.contrib.jmxscraper; +import static java.util.Arrays.asList; +import static java.util.Collections.singletonList; +import static java.util.Optional.ofNullable; +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.SEVERE; + import io.opentelemetry.api.GlobalOpenTelemetry; import io.opentelemetry.contrib.jmxscraper.config.JmxScraperConfig; import io.opentelemetry.contrib.jmxscraper.config.PropertiesCustomizer; @@ -21,14 +27,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.logging.Level; import java.util.logging.Logger; import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; @@ -55,7 +57,7 @@ public static void main(String[] args) { // set log format System.setProperty("java.util.logging.SimpleFormatter.format", "%1$tF %1$tT %4$s %5$s%n"); - List effectiveArgs = new ArrayList<>(Arrays.asList(args)); + List effectiveArgs = new ArrayList<>(asList(args)); boolean testMode = effectiveArgs.remove(TEST_ARG); try { @@ -73,7 +75,7 @@ public static void main(String[] args) { JmxScraperConfig scraperConfig = configCustomizer.getScraperConfig(); long exportSeconds = scraperConfig.getSamplingInterval().toMillis() / 1000; - logger.log(Level.INFO, "metrics export interval (seconds) = " + exportSeconds); + logger.log(INFO, "metrics export interval (seconds) = " + exportSeconds); JmxMetricInsight service = JmxMetricInsight.createService( @@ -81,8 +83,8 @@ public static void main(String[] args) { JmxConnectorBuilder connectorBuilder = JmxConnectorBuilder.createNew(scraperConfig.getServiceUrl()); - Optional.ofNullable(scraperConfig.getUsername()).ifPresent(connectorBuilder::withUser); - Optional.ofNullable(scraperConfig.getPassword()).ifPresent(connectorBuilder::withPassword); + ofNullable(scraperConfig.getUsername()).ifPresent(connectorBuilder::withUser); + ofNullable(scraperConfig.getPassword()).ifPresent(connectorBuilder::withPassword); if (scraperConfig.isRegistrySsl()) { connectorBuilder.withSslRegistry(); @@ -95,20 +97,20 @@ public static void main(String[] args) { jmxScraper.start(); } } catch (ConfigurationException e) { - logger.log(Level.SEVERE, "invalid configuration: " + e.getMessage(), e); + logger.log(SEVERE, "invalid configuration: " + e.getMessage(), e); System.exit(1); } catch (InvalidArgumentException e) { - logger.log(Level.SEVERE, e.getMessage(), e); + logger.log(SEVERE, e.getMessage(), e); logger.info("Usage: java -jar [-test] [-config ]"); logger.info(" -test test JMX connection with provided configuration and exit"); logger.info( " -config provide configuration, where is - for stdin, or "); System.exit(1); } catch (IOException e) { - logger.log(Level.SEVERE, "Unable to connect ", e); + logger.log(SEVERE, "Unable to connect ", e); System.exit(2); } catch (RuntimeException e) { - logger.log(Level.SEVERE, e.getMessage(), e); + logger.log(SEVERE, e.getMessage(), e); System.exit(3); } } @@ -119,14 +121,14 @@ private static boolean testConnection(JmxConnectorBuilder connectorBuilder) { MBeanServerConnection connection = connector.getMBeanServerConnection(); Integer mbeanCount = connection.getMBeanCount(); if (mbeanCount > 0) { - logger.log(Level.INFO, "JMX connection test OK"); + logger.log(INFO, "JMX connection test OK"); return true; } else { - logger.log(Level.SEVERE, "JMX connection test ERROR"); + logger.log(SEVERE, "JMX connection test ERROR"); return false; } } catch (IOException e) { - logger.log(Level.SEVERE, "JMX connection test ERROR", e); + logger.log(SEVERE, "JMX connection test ERROR", e); return false; } } @@ -210,7 +212,7 @@ private void start() throws IOException { try (JMXConnector connector = client.build()) { MBeanServerConnection connection = connector.getMBeanServerConnection(); - service.startRemote(getMetricConfig(config), () -> Collections.singletonList(connection)); + service.startRemote(getMetricConfig(config), () -> singletonList(connection)); running.set(true); logger.info("JMX scraping started"); diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java index 09a97eb83..62b6c38ea 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java @@ -5,15 +5,19 @@ package io.opentelemetry.contrib.jmxscraper.config; +import static java.util.Collections.emptyList; +import static java.util.Collections.emptySet; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableSet; +import static java.util.Locale.ROOT; + import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; import java.io.InputStream; import java.time.Duration; import java.util.ArrayList; -import java.util.Collections; import java.util.HashSet; import java.util.List; -import java.util.Locale; import java.util.Set; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -49,9 +53,9 @@ public class JmxScraperConfig { private String serviceUrl = ""; - private List jmxConfig = Collections.emptyList(); + private List jmxConfig = emptyList(); - private Set targetSystems = Collections.emptySet(); + private Set targetSystems = emptySet(); private TargetSystemSource targetSystemSource = TargetSystemSource.AUTO; @@ -73,7 +77,7 @@ public enum TargetSystemSource { static TargetSystemSource fromString(String source) { try { - return TargetSystemSource.valueOf(source.toUpperCase(Locale.ROOT)); + return TargetSystemSource.valueOf(source.toUpperCase(ROOT)); } catch (IllegalArgumentException e) { throw new IllegalArgumentException("Invalid target system source: " + source, e); } @@ -217,8 +221,8 @@ public static JmxScraperConfig fromConfig(ConfigProperties config) { "at least one of '" + JMX_TARGET_SYSTEM + "' or '" + JMX_CONFIG + "' must be set"); } - scraperConfig.jmxConfig = Collections.unmodifiableList(jmxConfig); - scraperConfig.targetSystems = Collections.unmodifiableSet(new HashSet<>(targetSystem)); + scraperConfig.jmxConfig = unmodifiableList(jmxConfig); + scraperConfig.targetSystems = unmodifiableSet(new HashSet<>(targetSystem)); scraperConfig.username = config.getString("otel.jmx.username"); scraperConfig.password = config.getString("otel.jmx.password"); diff --git a/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java b/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java index b51144360..430476d51 100644 --- a/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java +++ b/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/JmxScraperTest.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.jmxscraper; +import static java.util.Arrays.asList; +import static java.util.Collections.emptyList; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -12,8 +14,6 @@ import io.opentelemetry.contrib.jmxscraper.config.TestUtil; import java.io.IOException; import java.io.InputStream; -import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -32,7 +32,7 @@ void shouldThrowExceptionWhenInvalidCommandLineArgsProvided() { @Test void emptyArgumentsAllowed() throws InvalidArgumentException { - assertThat(JmxScraper.argsToConfig(Collections.emptyList())) + assertThat(JmxScraper.argsToConfig(emptyList())) .describedAs("empty config allowed to use JVM properties") .isEmpty(); } @@ -43,7 +43,7 @@ void shouldThrowExceptionWhenMissingProperties() { } private static void testInvalidArguments(String... args) { - assertThatThrownBy(() -> JmxScraper.argsToConfig(Arrays.asList(args))) + assertThatThrownBy(() -> JmxScraper.argsToConfig(asList(args))) .isInstanceOf(InvalidArgumentException.class); } @@ -54,7 +54,7 @@ void shouldCreateConfig_propertiesLoadedFromFile() throws InvalidArgumentExcepti // Windows returns /C:/path/to/file, which is not a valid path for Path.get() in Java. String filePath = ClassLoader.getSystemClassLoader().getResource("validConfig.properties").getPath(); - List args = Arrays.asList("-config", filePath); + List args = asList("-config", filePath); // When Properties parsedConfig = JmxScraper.argsToConfig(args); @@ -73,7 +73,7 @@ void shouldCreateConfig_propertiesLoadedFromStdIn() throws InvalidArgumentExcept ClassLoader.getSystemClassLoader().getResourceAsStream("validConfig.properties")) { // Given System.setIn(stream); - List args = Arrays.asList("-config", "-"); + List args = asList("-config", "-"); // When Properties parsedConfig = JmxScraper.argsToConfig(args); diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeter.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeter.java index e8a6b827c..2a080c8e8 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeter.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeter.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.metrics.micrometer; +import static java.util.Objects.requireNonNull; + import io.opentelemetry.api.metrics.DoubleGaugeBuilder; import io.opentelemetry.api.metrics.DoubleHistogramBuilder; import io.opentelemetry.api.metrics.LongCounterBuilder; @@ -15,7 +17,6 @@ import io.opentelemetry.contrib.metrics.micrometer.internal.instruments.MicrometerLongCounter; import io.opentelemetry.contrib.metrics.micrometer.internal.instruments.MicrometerLongUpDownCounter; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterSharedState; -import java.util.Objects; final class MicrometerMeter implements Meter { final MeterSharedState meterSharedState; @@ -26,25 +27,25 @@ final class MicrometerMeter implements Meter { @Override public LongCounterBuilder counterBuilder(String name) { - Objects.requireNonNull(name, "name"); + requireNonNull(name, "name"); return MicrometerLongCounter.builder(meterSharedState, name); } @Override public LongUpDownCounterBuilder upDownCounterBuilder(String name) { - Objects.requireNonNull(name, "name"); + requireNonNull(name, "name"); return MicrometerLongUpDownCounter.builder(meterSharedState, name); } @Override public DoubleHistogramBuilder histogramBuilder(String name) { - Objects.requireNonNull(name, "name"); + requireNonNull(name, "name"); return MicrometerDoubleHistogram.builder(meterSharedState, name); } @Override public DoubleGaugeBuilder gaugeBuilder(String name) { - Objects.requireNonNull(name, "name"); + requireNonNull(name, "name"); return MicrometerDoubleGauge.builder(meterSharedState, name); } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProvider.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProvider.java index d76e19a34..2817754e2 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProvider.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProvider.java @@ -5,12 +5,13 @@ package io.opentelemetry.contrib.metrics.micrometer; +import static java.util.Objects.requireNonNull; + import io.micrometer.core.instrument.MeterRegistry; import io.opentelemetry.api.metrics.MeterBuilder; import io.opentelemetry.api.metrics.MeterProvider; import io.opentelemetry.contrib.metrics.micrometer.internal.MemoizingSupplier; import io.opentelemetry.contrib.metrics.micrometer.internal.state.MeterProviderSharedState; -import java.util.Objects; import java.util.function.Supplier; /** @@ -44,13 +45,13 @@ public void close() { /** {@inheritDoc} */ @Override public MeterBuilder meterBuilder(String instrumentationScopeName) { - Objects.requireNonNull(instrumentationScopeName, "instrumentationScopeName"); + requireNonNull(instrumentationScopeName, "instrumentationScopeName"); return new MicrometerMeterBuilder(meterProviderSharedState, instrumentationScopeName); } /** Returns a new builder instance for this provider with the specified {@link MeterRegistry}. */ public static MicrometerMeterProviderBuilder builder(MeterRegistry meterRegistry) { - Objects.requireNonNull(meterRegistry, "meterRegistry"); + requireNonNull(meterRegistry, "meterRegistry"); return new MicrometerMeterProviderBuilder(() -> meterRegistry); } @@ -62,7 +63,7 @@ public static MicrometerMeterProviderBuilder builder(MeterRegistry meterRegistry */ public static MicrometerMeterProviderBuilder builder( Supplier meterRegistrySupplier) { - Objects.requireNonNull(meterRegistrySupplier, "meterRegistrySupplier"); + requireNonNull(meterRegistrySupplier, "meterRegistrySupplier"); return new MicrometerMeterProviderBuilder(new MemoizingSupplier<>(meterRegistrySupplier)); } } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrar.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrar.java index d9021a234..e3c34b2bb 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrar.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrar.java @@ -5,8 +5,9 @@ package io.opentelemetry.contrib.metrics.micrometer; +import static java.util.Objects.requireNonNull; + import java.util.List; -import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; @@ -39,7 +40,7 @@ public final class ScheduledCallbackRegistrar implements CallbackRegistrar { public static ScheduledCallbackRegistrarBuilder builder( ScheduledExecutorService scheduledExecutorService) { - Objects.requireNonNull(scheduledExecutorService, "scheduledExecutorService"); + requireNonNull(scheduledExecutorService, "scheduledExecutorService"); return new ScheduledCallbackRegistrarBuilder(scheduledExecutorService); } diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrarBuilder.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrarBuilder.java index de0da54f7..fb7ff0326 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrarBuilder.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/ScheduledCallbackRegistrarBuilder.java @@ -5,9 +5,10 @@ package io.opentelemetry.contrib.metrics.micrometer; +import static java.util.Objects.requireNonNull; + import com.google.errorprone.annotations.CanIgnoreReturnValue; import java.time.Duration; -import java.util.Objects; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; @@ -27,7 +28,7 @@ public final class ScheduledCallbackRegistrarBuilder { /** Sets the period between successive executions of each registered callback */ @CanIgnoreReturnValue public ScheduledCallbackRegistrarBuilder setPeriod(long period, TimeUnit unit) { - Objects.requireNonNull(unit, "unit"); + requireNonNull(unit, "unit"); this.period = period; this.timeUnit = unit; return this; @@ -36,7 +37,7 @@ public ScheduledCallbackRegistrarBuilder setPeriod(long period, TimeUnit unit) { /** Sets the period between successive executions of each registered callback */ @CanIgnoreReturnValue public ScheduledCallbackRegistrarBuilder setPeriod(Duration period) { - Objects.requireNonNull(period, "period"); + requireNonNull(period, "period"); this.period = period.toMillis(); this.timeUnit = TimeUnit.MILLISECONDS; return this; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java index ec453bf6f..8ff386251 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/connectivity/websocket/OkHttpWebSocket.java @@ -5,7 +5,8 @@ package io.opentelemetry.opamp.client.internal.connectivity.websocket; -import java.util.Objects; +import static java.util.Objects.requireNonNull; + import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -65,7 +66,7 @@ public void close(int code, @Nullable String reason) { } private okhttp3.WebSocket getWebSocket() { - return Objects.requireNonNull(webSocket.get()); + return requireNonNull(webSocket.get()); } private class ListenerAdapter extends WebSocketListener { diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java index 89ba8b1cb..be2d34215 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/state/InMemoryState.java @@ -5,7 +5,8 @@ package io.opentelemetry.opamp.client.internal.state; -import java.util.Objects; +import static java.util.Objects.requireNonNull; + import java.util.concurrent.atomic.AtomicReference; import javax.annotation.Nonnull; @@ -33,6 +34,6 @@ public void set(T value) { @Nonnull @Override public T get() { - return Objects.requireNonNull(state.get()); + return requireNonNull(state.get()); } } diff --git a/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java b/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java index 874895340..3b775a527 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java @@ -79,7 +79,7 @@ public SdkLoggerProviderBuilder apply( logRecordData -> { SpanContext spanContext = logRecordData.getSpanContext(); return spanContext.isSampled(); - }) {}) + })) .build() .get("TestScope"); } diff --git a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/MetricAdapter.java b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/MetricAdapter.java index c0b381e7d..e71331964 100644 --- a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/MetricAdapter.java +++ b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/MetricAdapter.java @@ -6,6 +6,8 @@ package io.opentelemetry.contrib.metrics.prometheus.clientbridge; import static io.prometheus.client.Collector.doubleToGoString; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.util.concurrent.TimeUnit.NANOSECONDS; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanContext; @@ -28,7 +30,6 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; -import java.util.concurrent.TimeUnit; import java.util.function.Function; import javax.annotation.Nullable; @@ -54,6 +55,8 @@ final class MetricAdapter { static final String LABEL_NAME_QUANTILE = "quantile"; static final String LABEL_NAME_LE = "le"; + static final Function sanitizer = new NameSanitizer(); + // Converts a MetricData to a Prometheus MetricFamilySamples. static MetricFamilySamples toMetricFamilySamples(MetricData metricData) { String cleanMetricName = cleanMetricName(metricData.getName()); @@ -99,8 +102,6 @@ static Collector.Type toMetricFamilyType(MetricData metricData) { return Collector.Type.UNKNOWN; } - static final Function sanitizer = new NameSanitizer(); - // Converts a list of points from MetricData to a list of Prometheus Samples. static List toSamples( String name, MetricDataType type, Collection points) { @@ -291,7 +292,7 @@ private static Sample createSample( labelValues, value, toPrometheusExemplar(exemplar), - TimeUnit.MILLISECONDS.convert(timestampNanos, TimeUnit.NANOSECONDS)); + MILLISECONDS.convert(timestampNanos, NANOSECONDS)); } return new Sample( name, @@ -299,7 +300,7 @@ private static Sample createSample( labelValues, value, null, - TimeUnit.MILLISECONDS.convert(timestampNanos, TimeUnit.NANOSECONDS)); + MILLISECONDS.convert(timestampNanos, NANOSECONDS)); } private static io.prometheus.client.exemplars.Exemplar toPrometheusExemplar( @@ -309,7 +310,7 @@ private static io.prometheus.client.exemplars.Exemplar toPrometheusExemplar( return new io.prometheus.client.exemplars.Exemplar( getExemplarValue(exemplar), // Convert to ms for prometheus, truncate nanosecond precision. - TimeUnit.NANOSECONDS.toMillis(exemplar.getEpochNanos()), + NANOSECONDS.toMillis(exemplar.getEpochNanos()), "trace_id", spanContext.getTraceId(), "span_id", diff --git a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java index 3acc1096a..e9c574f92 100644 --- a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java +++ b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/PrometheusCollector.java @@ -5,6 +5,8 @@ package io.opentelemetry.contrib.metrics.prometheus.clientbridge; +import static java.util.Collections.unmodifiableList; + import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -15,7 +17,6 @@ import io.prometheus.client.CollectorRegistry; import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.function.Supplier; @@ -79,7 +80,7 @@ public List collect() { for (MetricData metricData : allMetrics) { allSamples.add(MetricAdapter.toMetricFamilySamples(metricData)); } - return Collections.unmodifiableList(allSamples); + return unmodifiableList(allSamples); } } } diff --git a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/Serializer.java b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/Serializer.java index d696a14b7..d79892a27 100644 --- a/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/Serializer.java +++ b/prometheus-client-bridge/src/main/java/io/opentelemetry/contrib/metrics/prometheus/clientbridge/Serializer.java @@ -21,11 +21,12 @@ package io.opentelemetry.contrib.metrics.prometheus.clientbridge; +import static java.util.Collections.emptyList; + import io.opentelemetry.sdk.metrics.data.HistogramPointData; import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.PointData; import java.util.Collection; -import java.util.Collections; import java.util.List; /** Serializes metrics into Prometheus exposition formats. */ @@ -71,7 +72,7 @@ static Collection getPoints(MetricData metricData) { case EXPONENTIAL_HISTOGRAM: return metricData.getExponentialHistogramData().getPoints(); } - return Collections.emptyList(); + return emptyList(); } private Serializer() {} 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..3a427b7e3 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 @@ -5,6 +5,9 @@ package io.opentelemetry.contrib.stacktrace; +import static java.util.logging.Level.FINE; +import static java.util.logging.Level.SEVERE; + import com.google.auto.service.AutoService; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; @@ -50,7 +53,7 @@ static long getMinDuration(ConfigProperties properties) { log.fine("Stack traces capture is disabled"); } else { log.log( - Level.FINE, + FINE, "Stack traces will be added to spans with a minimum duration of {0} nanos", minDuration); } @@ -70,7 +73,7 @@ static Predicate getFilterPredicate(ConfigProperties properties) { if (filter == null) { // if value is set, lack of filtering is likely an error and must be reported - Level disabledLogLevel = filterClass != null ? Level.SEVERE : Level.FINE; + Level disabledLogLevel = filterClass != null ? SEVERE : FINE; log.log(disabledLogLevel, "Span stacktrace filtering disabled"); return span -> true; } else { From 2a04aabb97a8e675806f4dae41ac81ee545f605d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 2 Sep 2025 13:29:06 -0700 Subject: [PATCH 236/371] fix(deps): update dependency io.opentelemetry.proto:opentelemetry-proto to v1.8.0-alpha (#2200) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- disk-buffering/build.gradle.kts | 2 +- gcp-auth-extension/build.gradle.kts | 2 +- jmx-metrics/build.gradle.kts | 2 +- jmx-scraper/build.gradle.kts | 2 +- kafka-exporter/build.gradle.kts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 641c62207..ec6ef7c1f 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -30,7 +30,7 @@ dependencies { api("com.google.errorprone:error_prone_annotations:2.41.0") api("com.google.errorprone:error_prone_core:2.41.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") - api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") + api("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") api("io.prometheus:simpleclient:0.16.0") api("io.prometheus:simpleclient_common:0.16.0") api("io.prometheus:simpleclient_httpserver:0.16.0") diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index 0a883cf73..d851f58de 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -23,7 +23,7 @@ dependencies { testImplementation("org.mockito:mockito-inline") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - protos("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha@jar") + protos("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha@jar") } jmh { diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index f378df0d9..6a86c5d0d 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -42,7 +42,7 @@ dependencies { testImplementation("org.mockito:mockito-inline") testImplementation("org.mockito:mockito-junit-jupiter") testImplementation("org.mock-server:mockserver-netty:5.15.0") - testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") + testImplementation("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") testImplementation("org.springframework.boot:spring-boot-starter-web:2.7.18") testImplementation("org.springframework.boot:spring-boot-starter:2.7.18") testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18") diff --git a/jmx-metrics/build.gradle.kts b/jmx-metrics/build.gradle.kts index d5376e670..bc895cfc5 100644 --- a/jmx-metrics/build.gradle.kts +++ b/jmx-metrics/build.gradle.kts @@ -47,7 +47,7 @@ testing { dependencies { implementation("com.linecorp.armeria:armeria-grpc") implementation("com.linecorp.armeria:armeria-junit5") - implementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") + implementation("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") implementation("org.testcontainers:junit-jupiter") implementation("org.slf4j:slf4j-simple") } diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 9e2878549..687b30612 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -39,7 +39,7 @@ testing { implementation("org.slf4j:slf4j-simple") implementation("com.linecorp.armeria:armeria-junit5") implementation("com.linecorp.armeria:armeria-grpc") - implementation("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") + implementation("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") implementation("org.bouncycastle:bcprov-jdk18on:1.81") implementation("org.bouncycastle:bcpkix-jdk18on:1.81") } diff --git a/kafka-exporter/build.gradle.kts b/kafka-exporter/build.gradle.kts index d4aed3f54..5f189f97a 100644 --- a/kafka-exporter/build.gradle.kts +++ b/kafka-exporter/build.gradle.kts @@ -14,7 +14,7 @@ otelJava { dependencies { api("io.opentelemetry:opentelemetry-sdk-trace") api("io.opentelemetry:opentelemetry-sdk-common") - api("io.opentelemetry.proto:opentelemetry-proto:1.7.0-alpha") + api("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") api("org.apache.kafka:kafka-clients") compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") From fa6a9c79e0d285e0395fd5a8af4181d52848ce32 Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Tue, 2 Sep 2025 22:03:51 +0100 Subject: [PATCH 237/371] Add dynamically changing the inferred span interval (#2153) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: Trask Stalnaker --- .../contrib/inferredspans/InferredSpans.java | 51 +++++++++++ .../inferredspans/InferredSpansProcessor.java | 8 ++ .../InferredSpansProcessorBuilder.java | 7 +- .../internal/InferredSpansConfiguration.java | 6 +- .../internal/SamplingProfiler.java | 18 +++- .../inferredspans/InferredSpansTest.java | 88 +++++++++++++++++++ 6 files changed, 173 insertions(+), 5 deletions(-) create mode 100644 inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java create mode 100644 inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java new file mode 100644 index 000000000..76f55db83 --- /dev/null +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java @@ -0,0 +1,51 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.inferredspans; + +import java.time.Duration; +import javax.annotation.Nullable; + +/** + * A global accessor for the {@link InferredSpansProcessor} instance. + * + *

This class is for internal use only and may be removed in a future release. + */ +public final class InferredSpans { + + @Nullable private static volatile InferredSpansProcessor instance; + + private InferredSpans() {} + + /** + * Sets the {@link InferredSpansProcessor} instance. + * + * @param processor the processor instance + */ + public static void setInstance(@Nullable InferredSpansProcessor processor) { + instance = processor; + } + + /** + * Returns whether inferred spans are enabled. + * + * @return whether inferred spans are enabled + */ + public static boolean isEnabled() { + return instance != null; + } + + /** + * Sets the profiler interval. + * + * @param interval the new profiler interval + */ + public static void setProfilerInterval(Duration interval) { + InferredSpansProcessor p = instance; + if (p != null) { + p.setProfilerInterval(interval); + } + } +} diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java index a9288bac2..19baf3174 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java @@ -21,6 +21,7 @@ import java.io.File; import java.io.IOException; import java.io.InputStream; +import java.time.Duration; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; @@ -38,6 +39,7 @@ public class InferredSpansProcessor implements SpanProcessor { // Visible for testing final SamplingProfiler profiler; + private final InferredSpansConfiguration config; private Supplier tracerProvider = GlobalOpenTelemetry::getTracerProvider; @Nullable private volatile Tracer tracer; @@ -48,12 +50,18 @@ public class InferredSpansProcessor implements SpanProcessor { boolean startScheduledProfiling, @Nullable File activationEventsFile, @Nullable File jfrFile) { + this.config = config; profiler = new SamplingProfiler(config, clock, this::getTracer, activationEventsFile, jfrFile); if (startScheduledProfiling) { profiler.start(); } } + public void setProfilerInterval(Duration interval) { + config.setProfilerInterval(interval); + profiler.reschedule(); + } + public static InferredSpansProcessorBuilder builder() { return new InferredSpansProcessorBuilder(); } diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java index 3124f3341..e8f52ed68 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessorBuilder.java @@ -71,8 +71,11 @@ public InferredSpansProcessor build() { profilingDuration, profilerLibDirectory, parentOverrideHandler); - return new InferredSpansProcessor( - config, clock, startScheduledProfiling, activationEventsFile, jfrFile); + InferredSpansProcessor processor = + new InferredSpansProcessor( + config, clock, startScheduledProfiling, activationEventsFile, jfrFile); + InferredSpans.setInstance(processor); + return processor; } /** diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java index 5091a36a5..819a5b8cf 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java @@ -23,7 +23,7 @@ public class InferredSpansConfiguration { private final Duration inferredSpansMinDuration; private final List includedClasses; private final List excludedClasses; - private final Duration profilerInterval; + private volatile Duration profilerInterval; private final Duration profilingDuration; @Nullable private final String profilerLibDirectory; private final BiConsumer parentOverrideHandler; @@ -84,6 +84,10 @@ public Duration getProfilingInterval() { return profilerInterval; } + public void setProfilerInterval(Duration profilerInterval) { + this.profilerInterval = profilerInterval; + } + public Duration getProfilingDuration() { return profilingDuration; } diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java index 61e5f75a7..27d0e5305 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfiler.java @@ -39,6 +39,7 @@ import java.util.Locale; import java.util.Objects; import java.util.concurrent.Executors; +import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; @@ -151,6 +152,7 @@ public class SamplingProfiler implements Runnable { private final Supplier tracerProvider; private final AsyncProfiler profiler; + @Nullable private volatile Future profilingTask; /** * Creates a sampling profiler, optionally relying on existing files. @@ -385,7 +387,7 @@ public void run() { if (!interrupted && !scheduler.isShutdown()) { long delay = config.getProfilingInterval().toMillis() - profilingDuration.toMillis(); - scheduler.schedule(this, delay, TimeUnit.MILLISECONDS); + profilingTask = scheduler.schedule(this, delay, TimeUnit.MILLISECONDS); } } @@ -723,7 +725,19 @@ public void copyFromFiles(Path activationEvents, Path traces) throws IOException @SuppressWarnings("FutureReturnValueIgnored") public void start() { - scheduler.submit(this); + profilingTask = scheduler.submit(this); + } + + @SuppressWarnings({"FutureReturnValueIgnored", "Interruption"}) + public void reschedule() { + Future future = this.profilingTask; + if (future != null) { + if (future.cancel(true)) { + Duration profilingDuration = config.getProfilingDuration(); + long delay = config.getProfilingInterval().toMillis() - profilingDuration.toMillis(); + profilingTask = scheduler.schedule(this, delay, TimeUnit.MILLISECONDS); + } + } } public void stop() throws InterruptedException, IOException { diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java new file mode 100644 index 000000000..e0de5997e --- /dev/null +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansTest.java @@ -0,0 +1,88 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.inferredspans; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +import io.opentelemetry.contrib.inferredspans.internal.SamplingProfiler; +import io.opentelemetry.contrib.inferredspans.internal.util.DisabledOnOpenJ9; +import java.time.Duration; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnOs; +import org.junit.jupiter.api.condition.OS; + +@DisabledOnOs(OS.WINDOWS) +@DisabledOnOpenJ9 +class InferredSpansTest { + + private ProfilerTestSetup setup; + + @BeforeEach + void setUp() { + InferredSpans.setInstance(null); + } + + @AfterEach + void tearDown() { + if (setup != null) { + setup.close(); + } + InferredSpans.setInstance(null); + } + + @Test + void testIsEnabled() { + assertThat(InferredSpans.isEnabled()).isFalse(); + + setup = ProfilerTestSetup.create(c -> {}); + + assertThat(InferredSpans.isEnabled()).isTrue(); + + setup.close(); + setup = null; + + // In a real-world scenario, the close() method would lead to the processor being garbage + // collected, but to make it deterministic, we manually set the instance to null + InferredSpans.setInstance(null); + assertThat(InferredSpans.isEnabled()).isFalse(); + } + + @Test + void testSetProfilerIntervalWhenDisabled() { + InferredSpans.setProfilerInterval(Duration.ofMillis(10)); + + setup = + ProfilerTestSetup.create( + c -> + c.profilerInterval(Duration.ofSeconds(10)) + .profilingDuration(Duration.ofMillis(500))); + + // assert that the interval set before the profiler was initialized is ignored + assertThat(setup.profiler.getConfig().getProfilingInterval()).isEqualTo(Duration.ofSeconds(10)); + } + + @Test + void testSetProfilerInterval() { + setup = + ProfilerTestSetup.create( + c -> + c.profilerInterval(Duration.ofSeconds(10)) + .profilingDuration(Duration.ofMillis(500))); + + SamplingProfiler profiler = setup.profiler; + await() + .untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(1)); + + InferredSpans.setProfilerInterval(Duration.ofMillis(100)); + + await() + .timeout(Duration.ofSeconds(2)) + .untilAsserted(() -> assertThat(profiler.getProfilingSessions()).isGreaterThanOrEqualTo(2)); + } +} From 352d2be779040671e69e2c0cd0e30c49c5cf4592 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 5 Sep 2025 09:11:52 -0700 Subject: [PATCH 238/371] fix(deps): update all patch versions to v9.4.3.1 (#2205) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- ibm-mq-metrics/build.gradle.kts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 3b43769c2..d72192e15 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -32,7 +32,7 @@ dependencies { api("com.google.code.findbugs:jsr305:3.0.2") api("io.swagger:swagger-annotations:1.6.16") api("org.jetbrains:annotations:26.0.2-1") - api("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0") + api("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1") api("org.yaml:snakeyaml:2.4") api("com.fasterxml.jackson.core:jackson-databind:2.19.2") api("io.opentelemetry:opentelemetry-sdk") @@ -45,11 +45,11 @@ dependencies { integrationTestImplementation("org.assertj:assertj-core:3.27.4") integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4") integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.0") + integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.1") integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.4") integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4") - ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0") { + ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1") { artifact { name = "com.ibm.mq.allclient" extension = "jar" @@ -60,7 +60,7 @@ dependencies { tasks.shadowJar { dependencies { - exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.3.0")) + exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1")) } } From d96a03008890dbda7b750f6a00663f6b9bbd8b0b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 5 Sep 2025 18:13:20 +0200 Subject: [PATCH 239/371] add baggage before it can be exported (#2152) Co-authored-by: Trask Stalnaker --- .../baggage/processor/BaggageProcessorCustomizer.java | 6 ++++-- .../processor/BaggageProcessorCustomizerTest.java | 10 +++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java index da35512a3..e45c616fc 100644 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java @@ -37,7 +37,8 @@ private static void addSpanProcessor( return; } - sdkTracerProviderBuilder.addSpanProcessor(createBaggageSpanProcessor(keys)); + // need to add before the batch span processor + sdkTracerProviderBuilder.addSpanProcessorFirst(createBaggageSpanProcessor(keys)); } static BaggageSpanProcessor createBaggageSpanProcessor(List keys) { @@ -56,7 +57,8 @@ private static void addLogRecordProcessor( return; } - sdkLoggerProviderBuilder.addLogRecordProcessor(createBaggageLogRecordProcessor(keys)); + // need to add before the batch span processor + sdkLoggerProviderBuilder.addLogRecordProcessorFirst(createBaggageLogRecordProcessor(keys)); } static BaggageLogRecordProcessor createBaggageLogRecordProcessor(List keys) { diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java index bfa8835ae..6d2e77469 100644 --- a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java @@ -48,11 +48,15 @@ class BaggageProcessorCustomizerTest { private static final String MEMORY_EXPORTER = "memory"; @Test - void test_customizer() { + void test_empty_customizer() { assertCustomizer( Collections.emptyMap(), span -> assertThat(span).hasTotalAttributeCount(0), logRecord -> assertThat(logRecord).hasTotalAttributeCount(0)); + } + + @Test + void test_customizer() { Map properties = new HashMap<>(); properties.put("otel.java.experimental.span-attributes.copy-from-baggage.include", "key"); properties.put("otel.java.experimental.log-attributes.copy-from-baggage.include", "key"); @@ -117,7 +121,7 @@ private static OpenTelemetrySdk getOpenTelemetrySdk( new ComponentLoader() { @Override public List load(Class spiClass) { - if (spiClass == ConfigurableSpanExporterProvider.class) { + if (spiClass.equals(ConfigurableSpanExporterProvider.class)) { return Collections.singletonList( spiClass.cast( new ConfigurableSpanExporterProvider() { @@ -132,7 +136,7 @@ public String getName() { return MEMORY_EXPORTER; } })); - } else if (spiClass == ConfigurableLogRecordExporterProvider.class) { + } else if (spiClass.equals(ConfigurableLogRecordExporterProvider.class)) { return Collections.singletonList( spiClass.cast( new ConfigurableLogRecordExporterProvider() { From 0a780027f7a265eae0c00ce38ff16ed774b5b2f2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 5 Sep 2025 21:21:22 +0200 Subject: [PATCH 240/371] use mise for easier local link checking (#2027) Co-authored-by: Trask Stalnaker --- .github/scripts/dependencies.Dockerfile | 3 -- .github/scripts/link-check.sh | 62 ----------------------- .github/workflows/reusable-link-check.yml | 8 +-- mise.toml | 11 ++++ 4 files changed, 16 insertions(+), 68 deletions(-) delete mode 100644 .github/scripts/dependencies.Dockerfile delete mode 100755 .github/scripts/link-check.sh create mode 100644 mise.toml diff --git a/.github/scripts/dependencies.Dockerfile b/.github/scripts/dependencies.Dockerfile deleted file mode 100644 index d8e3bbfe1..000000000 --- a/.github/scripts/dependencies.Dockerfile +++ /dev/null @@ -1,3 +0,0 @@ -# this file exists so that Renovate can auto-update docker image versions that are then used elsewhere - -FROM lycheeverse/lychee:sha-2aa22f8@sha256:2e3786630482c41f9f2dd081e06d7da1c36d66996e8cf6573409b8bc418d48c4 AS lychee diff --git a/.github/scripts/link-check.sh b/.github/scripts/link-check.sh deleted file mode 100755 index 7e6b6910a..000000000 --- a/.github/scripts/link-check.sh +++ /dev/null @@ -1,62 +0,0 @@ -#!/bin/bash - -set -e - -export MSYS_NO_PATHCONV=1 # for Git Bash on Windows - -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -ROOT_DIR="$SCRIPT_DIR/../.." -DEPENDENCIES_DOCKERFILE="$SCRIPT_DIR/dependencies.Dockerfile" - -# Parse command line arguments -LOCAL_LINKS_ONLY=false -TARGET="" - -while [[ $# -gt 0 ]]; do - case $1 in - --local-links-only) - LOCAL_LINKS_ONLY=true - shift - ;; - *) - # Treat any other arguments as file paths - TARGET="$TARGET $1" - shift - ;; - esac -done - -# Extract lychee version from dependencies.dockerfile -LYCHEE_VERSION=$(grep "FROM lycheeverse/lychee:" "$DEPENDENCIES_DOCKERFILE" | sed 's/.*FROM lycheeverse\/lychee:\([^ ]*\).*/\1/') - -if [[ -z "$TARGET" ]]; then - TARGET="." -fi - -# Build the lychee command with optional GitHub token -CMD="lycheeverse/lychee:$LYCHEE_VERSION --verbose --root-dir /data" - -# Add GitHub token if available -if [[ -n "$GITHUB_TOKEN" ]]; then - CMD="$CMD --github-token $GITHUB_TOKEN" -fi - -if [[ "$LOCAL_LINKS_ONLY" == "true" ]]; then - CMD="$CMD --scheme file --include-fragments" -else - CMD="$CMD --config .github/scripts/lychee-config.toml" -fi - -CMD="$CMD $TARGET" - -# Determine if we should allocate a TTY -DOCKER_FLAGS="--rm --init" -if [[ -t 0 ]]; then - DOCKER_FLAGS="$DOCKER_FLAGS -it" -else - DOCKER_FLAGS="$DOCKER_FLAGS -i" -fi - -# Run lychee with proper signal handling -# shellcheck disable=SC2086 -exec docker run $DOCKER_FLAGS -v "$ROOT_DIR":/data -w /data $CMD diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 8dccc28ca..1cdfdba5e 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -14,11 +14,13 @@ jobs: with: fetch-depth: 0 # needed for merge-base below + - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 + - name: Link check - relative links (all files) if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} - run: ./.github/scripts/link-check.sh --local-links-only + run: mise run lint-local-links - name: Get modified files if: github.event_name == 'pull_request' @@ -50,10 +52,10 @@ jobs: if: github.event_name == 'pull_request' && steps.modified-files.outputs.files != '' env: GITHUB_TOKEN: ${{ github.token }} - run: ./.github/scripts/link-check.sh ${{ steps.modified-files.outputs.files }} + run: mise run lint-links ${{ steps.modified-files.outputs.files }} - name: Link check - all links (all files) if: github.event_name != 'pull_request' || steps.config-check.outputs.modified == 'true' env: GITHUB_TOKEN: ${{ github.token }} - run: ./.github/scripts/link-check.sh + run: mise run lint-links diff --git a/mise.toml b/mise.toml new file mode 100644 index 000000000..c37d5027f --- /dev/null +++ b/mise.toml @@ -0,0 +1,11 @@ +[tools] +lychee = "0.18.1" + +[tasks.lint-local-links] +run = 'lychee --verbose --scheme file --include-fragments {{arg(name="files", default=".")}}' + +[tasks.lint-links] +run = 'lychee --verbose --config .github/scripts/lychee-config.toml {{arg(name="files", default=".")}}' + +[settings] +idiomatic_version_file_enable_tools = [] From 56c53a2b50f11ee3b83c10f4346617fb678131bf Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 07:39:49 +0300 Subject: [PATCH 241/371] chore(deps): update plugin com.gradleup.shadow to v9.1.0 (#2225) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 7d9db17b1..13de5fd7e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - id("com.gradleup.shadow") version "9.0.2" + id("com.gradleup.shadow") version "9.1.0" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("com.gradle.develocity") version "4.1.1" } From 35d41a6410de5b3b33aaf3951ce8668bc32e1c7a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 07:41:22 +0300 Subject: [PATCH 242/371] chore(deps): update actions/github-script action to v7.1.0 (#2223) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-issue-owners.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 232396d9c..b27081cf9 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -23,7 +23,7 @@ jobs: run: npm install js-yaml - name: Parse component label and assign owners - uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 with: script: | const fs = require('fs'); From ed70d3fbdfeb71a9f25261db9227d5753eacbd61 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 7 Sep 2025 21:42:28 -0700 Subject: [PATCH 243/371] Pin npm dependency version (#2211) --- .github/renovate.json5 | 10 ++++++++++ .github/workflows/assign-issue-owners.yml | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 5b8203753..a33bfc0ed 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -190,6 +190,16 @@ 'npx (?[^@]+)@(?[^\\s]+)', ], }, + { + customType: 'regex', + datasourceTemplate: 'npm', + managerFilePatterns: [ + '.github/workflows/**', + ], + matchStrings: [ + 'npm install (?[^@\\s]+)@(?[^\\s]+)', + ], + }, { customType: 'regex', datasourceTemplate: 'java-version', diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index b27081cf9..01b32aafe 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install js-yaml - run: npm install js-yaml + run: npm install js-yaml@4.0.0 - name: Parse component label and assign owners uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 From 0fa1f92c4dcc0549f589c8eb225447da48cf0a56 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 7 Sep 2025 21:43:17 -0700 Subject: [PATCH 244/371] Switch component owner action (#2209) --- .github/workflows/assign-reviewers.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml index 5994bd2e1..53c0d509a 100644 --- a/.github/workflows/assign-reviewers.yml +++ b/.github/workflows/assign-reviewers.yml @@ -18,6 +18,6 @@ jobs: pull-requests: write # for assigning reviewers runs-on: ubuntu-latest steps: - - uses: open-telemetry/assign-reviewers-action@2f4f06ccc561740d5094d9ca5e66dc2392d13e8f # main + - uses: dyladan/component-owners@58bd86e9814d23f1525d0a970682cead459fa783 # v0.1.0 with: config-file: .github.amrom.workers.devponent_owners.yml From 31f58a39eb3a5b95fd9fc5dcdf3fbec5448c150c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 04:44:55 +0000 Subject: [PATCH 245/371] chore(deps): update plugin com.squareup.wire to v5.4.0 (#2226) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- disk-buffering/build.gradle.kts | 2 +- opamp-client/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/disk-buffering/build.gradle.kts b/disk-buffering/build.gradle.kts index d851f58de..c36d73588 100644 --- a/disk-buffering/build.gradle.kts +++ b/disk-buffering/build.gradle.kts @@ -6,7 +6,7 @@ plugins { id("otel.animalsniffer-conventions") id("com.gradleup.shadow") id("me.champeau.jmh") version "0.7.3" - id("com.squareup.wire") version "5.3.11" + id("com.squareup.wire") version "5.4.0" } description = "Exporter implementations that store signals on disk" diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 47e64f778..0e890cedf 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -5,7 +5,7 @@ plugins { id("otel.publish-conventions") id("otel.animalsniffer-conventions") id("de.undercouch.download") version "5.6.0" - id("com.squareup.wire") version "5.3.11" + id("com.squareup.wire") version "5.4.0" } description = "Client implementation of the OpAMP spec." From 8299175d13787d5f179020b24dc98bdb3e99aef1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:03:29 +0300 Subject: [PATCH 246/371] fix(deps): update dependency com.google.auth:google-auth-library-oauth2-http to v1.39.0 (#2229) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gcp-auth-extension/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 6a86c5d0d..9803ef080 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -24,7 +24,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.38.0") + implementation("com.google.auth:google-auth-library-oauth2-http:1.39.0") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From 968cfae2dc6580a71c2f0b2fcd8d7e9d28f8b214 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:03:47 +0300 Subject: [PATCH 247/371] fix(deps): update dependency org.apache.kafka:kafka-clients to v4.1.0 (#2230) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index ec6ef7c1f..904f8fd12 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -51,7 +51,7 @@ dependencies { api("org.bouncycastle:bcpkix-jdk15on:1.70") api("org.junit-pioneer:junit-pioneer:1.9.1") api("org.skyscreamer:jsonassert:1.5.3") - api("org.apache.kafka:kafka-clients:4.0.0") + api("org.apache.kafka:kafka-clients:4.1.0") api("org.testcontainers:kafka:1.21.3") api("com.lmax:disruptor:3.4.4") api("org.jctools:jctools-core:4.0.5") From 2dd3ec27099f1be5086037a7e0020478f63376cd Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:04:16 +0300 Subject: [PATCH 248/371] fix(deps): update dependency org.yaml:snakeyaml to v2.5 (#2231) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- ibm-mq-metrics/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index d72192e15..552417048 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -33,7 +33,7 @@ dependencies { api("io.swagger:swagger-annotations:1.6.16") api("org.jetbrains:annotations:26.0.2-1") api("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1") - api("org.yaml:snakeyaml:2.4") + api("org.yaml:snakeyaml:2.5") api("com.fasterxml.jackson.core:jackson-databind:2.19.2") api("io.opentelemetry:opentelemetry-sdk") api("io.opentelemetry:opentelemetry-exporter-otlp") From 74f0e875caf0b18e6d0d29aa840c63d6d5cc6afc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:04:34 +0300 Subject: [PATCH 249/371] chore(deps): update dependency js-yaml to v4.1.0 (#2232) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-issue-owners.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 01b32aafe..1d2649c1c 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install js-yaml - run: npm install js-yaml@4.0.0 + run: npm install js-yaml@4.1.0 - name: Parse component label and assign owners uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 From 58d94aa351346cb0acf674d1a355460b8e1629f3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:04:52 +0300 Subject: [PATCH 250/371] chore(deps): update actions/github-script action to v8 (#2233) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-issue-owners.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 1d2649c1c..80ec22a80 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -23,7 +23,7 @@ jobs: run: npm install js-yaml@4.1.0 - name: Parse component label and assign owners - uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); From 725890fd43f2cd8d925bd98dcef1f294004e1765 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 06:05:12 +0000 Subject: [PATCH 251/371] chore(deps): update actions/setup-java action to v5 (#2234) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-common.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 3ad7b8f5d..a6ecd1d86 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index bb9b2badb..84a87e609 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -22,7 +22,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Set up JDK for running Gradle - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 with: distribution: temurin java-version: 17 From d05ba6cf4909c8e4f5f238a869a59aa5378ab6ff Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 06:06:15 +0000 Subject: [PATCH 252/371] chore(deps): update github/codeql-action action to v3.30.1 (#2224) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 61b90c7f0..a6eb36e71 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 - name: Initialize CodeQL - uses: github/codeql-action/init@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 + uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 + uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 0c8d3af84..27a5edc25 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@3c3833e0f8c1c83d449a7478aa59c036a9165498 # v3.29.11 + uses: github/codeql-action/upload-sarif@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 with: sarif_file: results.sarif From ff7cbcbe56f3d69d0b7da9e1cd589075521fbfb9 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 8 Sep 2025 09:44:36 -0700 Subject: [PATCH 253/371] Minor updates to mise/lychee workflow (#2219) --- .github/{scripts/lychee-config.toml => config/lychee.toml} | 0 .github/workflows/reusable-link-check.yml | 2 +- mise.toml | 3 ++- 3 files changed, 3 insertions(+), 2 deletions(-) rename .github/{scripts/lychee-config.toml => config/lychee.toml} (100%) diff --git a/.github/scripts/lychee-config.toml b/.github/config/lychee.toml similarity index 100% rename from .github/scripts/lychee-config.toml rename to .github/config/lychee.toml diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 1cdfdba5e..9cfd9ff48 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -41,7 +41,7 @@ jobs: run: | merge_base=$(git merge-base origin/${{ github.base_ref }} HEAD) config_modified=$(git diff --name-only $merge_base...${{ github.event.pull_request.head.sha }} \ - | grep -E '\.github/scripts/(lychee-config\.toml|link-check\.sh|dependencies\.Dockerfile)$' || true) + | grep -E '^(\.github/config/lychee\.toml|mise\.toml)$' || true) if [ -n "$config_modified" ]; then echo "modified=true" >> $GITHUB_OUTPUT else diff --git a/mise.toml b/mise.toml index c37d5027f..e00ac63b2 100644 --- a/mise.toml +++ b/mise.toml @@ -5,7 +5,8 @@ lychee = "0.18.1" run = 'lychee --verbose --scheme file --include-fragments {{arg(name="files", default=".")}}' [tasks.lint-links] -run = 'lychee --verbose --config .github/scripts/lychee-config.toml {{arg(name="files", default=".")}}' +run = 'lychee --verbose --config .github/config/lychee.toml {{arg(name="files", var=true, default=".")}}' [settings] +# Only install tools explicitly defined in the [tools] section above idiomatic_version_file_enable_tools = [] From f827041e06706c628fec97de06bea8427ea76d43 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 09:44:57 -0700 Subject: [PATCH 254/371] chore(deps): update actions/stale action to v10 (#2235) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/issue-management-stale-action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index 483df9b15..7282d3fcd 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -16,7 +16,7 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: - - uses: actions/stale@5bef64f19d7facfb25b37b414482c7164d639639 # v9.1.0 + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 7 From b01b65fbffcbf836bd61db8c3f76f5260d4e64f2 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 8 Sep 2025 14:06:31 -0700 Subject: [PATCH 255/371] Update to actively maintained js yaml library (#2236) --- .github/workflows/assign-issue-owners.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 80ec22a80..05fcfc79f 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -19,15 +19,15 @@ jobs: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install js-yaml - run: npm install js-yaml@4.1.0 + - name: Install yaml + run: npm install yaml@2.8.0 - name: Parse component label and assign owners uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 with: script: | const fs = require('fs'); - const yaml = require('js-yaml'); + const { parse } = require('yaml'); // Extract component name from label const labelName = context.payload.label.name; @@ -42,7 +42,7 @@ jobs: // Read and parse component_owners.yml const yamlContent = fs.readFileSync('.github.amrom.workers.devponent_owners.yml', 'utf8'); - const data = yaml.load(yamlContent); + const data = parse(yamlContent); if (!data || !data.components) { core.setFailed('Invalid component_owners.yml structure'); From 3cf1b892d068865cc208cff17e3b7ceb4d91f317 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 8 Sep 2025 14:19:41 -0700 Subject: [PATCH 256/371] Add Jackson packages grouping to Renovate configuration and fix gitignore (#2237) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .github/renovate.json5 | 6 ++++++ .gitignore | 3 +++ 2 files changed, 9 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a33bfc0ed..ca0c996d3 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -144,6 +144,12 @@ 'com.google.errorprone{/,}**', ], }, + { + groupName: 'jackson packages', + matchPackageNames: [ + 'com.fasterxml.jackson{/,}**', + ], + }, { // pinned version for compatibility with java 8 JFR parsing matchUpdateTypes: [ diff --git a/.gitignore b/.gitignore index 641751d61..4743c99e3 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,6 @@ bin .swp .gitpod.yml + +# Gradle build scans +build-scan.txt From 84fec051aea55e3fbb4f42395f943231142b3c85 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 8 Sep 2025 15:24:50 -0700 Subject: [PATCH 257/371] Enable gradle cache configuration (#2222) --- .../kotlin/otel.java-conventions.gradle.kts | 5 ++-- .../otel.spotless-conventions.gradle.kts | 11 ++++---- gradle.properties | 2 ++ opamp-client/build.gradle.kts | 28 +++++++++++-------- 4 files changed, 28 insertions(+), 18 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index a02e2db1c..894e8bda3 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -106,12 +106,13 @@ plugins.withId("otel.publish-conventions") { register("generateVersionResource") { val moduleName = otelJava.moduleName val propertiesDir = moduleName.map { layout.buildDirectory.file("generated/properties/${it.replace('.', '/')}") } + val projectVersion = project.version.toString() - inputs.property("project.version", project.version.toString()) + inputs.property("project.version", projectVersion) outputs.dir(propertiesDir) doLast { - File(propertiesDir.get().get().asFile, "version.properties").writeText("contrib.version=${project.version}") + File(propertiesDir.get().get().asFile, "version.properties").writeText("contrib.version=${projectVersion}") } } } diff --git a/buildSrc/src/main/kotlin/otel.spotless-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.spotless-conventions.gradle.kts index b1c39dcd0..f3d387872 100644 --- a/buildSrc/src/main/kotlin/otel.spotless-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.spotless-conventions.gradle.kts @@ -8,11 +8,12 @@ spotless { licenseHeaderFile(rootProject.file("buildscripts/spotless.license.java"), "(package|import|public|// Includes work from:)") target("src/**/*.java") } - plugins.withId("groovy") { - groovy { - licenseHeaderFile(rootProject.file("buildscripts/spotless.license.java"), "(package|import|class)") - } - } + // commented out for now due to incompatibility with gradle cache configuration + // plugins.withId("groovy") { + // groovy { + // licenseHeaderFile(rootProject.file("buildscripts/spotless.license.java"), "(package|import|class)") + // } + // } plugins.withId("scala") { scala { scalafmt() diff --git a/gradle.properties b/gradle.properties index 0b5e135ed..bc596d5e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,5 +1,7 @@ org.gradle.parallel=true org.gradle.caching=true +org.gradle.configuration-cache=true +org.gradle.configuration-cache.parallel=true org.gradle.priority=low diff --git a/opamp-client/build.gradle.kts b/opamp-client/build.gradle.kts index 0e890cedf..8abbbee65 100644 --- a/opamp-client/build.gradle.kts +++ b/opamp-client/build.gradle.kts @@ -1,10 +1,11 @@ -import de.undercouch.gradle.tasks.download.DownloadExtension +import java.io.FileOutputStream +import java.io.InputStream +import java.net.URL plugins { id("otel.java-conventions") id("otel.publish-conventions") id("otel.animalsniffer-conventions") - id("de.undercouch.download") version "5.6.0" id("com.squareup.wire") version "5.4.0" } @@ -23,11 +24,11 @@ dependencies { testImplementation("com.squareup.okhttp3:mockwebserver3-junit5") } -val opampProtos = tasks.register("opampProtoDownload", download) -opampProtos.configure { +val opampProtos = tasks.register("opampProtoDownload") { group = "opamp" outputProtosDir.set(project.layout.buildDirectory.dir("opamp/protos")) - downloadedZipFile.set(project.layout.buildDirectory.file("intermediate/$name/release.zip")) + downloadedZipFile.set(project.layout.buildDirectory.file("intermediate/opampProtoDownload/release.zip")) + zipUrl.set("https://github.com/open-telemetry/opamp-spec/zipball/v0.14.0") } wire { @@ -37,8 +38,7 @@ wire { } } -abstract class DownloadOpampProtos @Inject constructor( - private val download: DownloadExtension, +abstract class DownloadAndExtractOpampProtos @Inject constructor( private val archiveOps: ArchiveOperations, private val fileOps: FileSystemOperations, ) : DefaultTask() { @@ -49,14 +49,20 @@ abstract class DownloadOpampProtos @Inject constructor( @get:Internal abstract val downloadedZipFile: RegularFileProperty + @get:Input + abstract val zipUrl: Property + @TaskAction fun execute() { - val zipUrl = "https://github.com/open-telemetry/opamp-spec/zipball/v0.14.0" + val url = URL(zipUrl.get()) + downloadedZipFile.get().asFile.parentFile.mkdirs() - download.run { - src(zipUrl) - dest(downloadedZipFile) + url.openStream().use { input: InputStream -> + downloadedZipFile.get().asFile.outputStream().use { output: FileOutputStream -> + input.copyTo(output) + } } + val protos = archiveOps.zipTree(downloadedZipFile).matching { setIncludes(listOf("**/*.proto")) } From d587944020c4d31fc850e96d9964db16a288c035 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 8 Sep 2025 19:33:37 -0700 Subject: [PATCH 258/371] fix(deps): update all patch versions (#2239) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/assign-issue-owners.yml | 2 +- micrometer-meter-provider/build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 05fcfc79f..11965eae5 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Install yaml - run: npm install yaml@2.8.0 + run: npm install yaml@2.8.1 - name: Parse component label and assign owners uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index 6d585cca5..27e61bf1f 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.15.3") + testImplementation("io.micrometer:micrometer-core:1.15.4") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.15.3") + implementation("io.micrometer:micrometer-registry-prometheus:1.15.4") } } } From 5a9d4154c4a0684b2905712b50bbb6cf2f373e4f Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 07:44:32 +0300 Subject: [PATCH 259/371] Add assign-owners: false to dyladan/component-owners workflow (#2243) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .github/workflows/assign-reviewers.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/assign-reviewers.yml b/.github/workflows/assign-reviewers.yml index 53c0d509a..d1cf86ce8 100644 --- a/.github/workflows/assign-reviewers.yml +++ b/.github/workflows/assign-reviewers.yml @@ -21,3 +21,4 @@ jobs: - uses: dyladan/component-owners@58bd86e9814d23f1525d0a970682cead459fa783 # v0.1.0 with: config-file: .github.amrom.workers.devponent_owners.yml + assign-owners: false From 940d4504e25b6d4d2b9bf82427e92240aa316582 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 9 Sep 2025 04:45:11 +0000 Subject: [PATCH 260/371] Update Jackson packages to v2.20.0 (#2242) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trask <218610+trask@users.noreply.github.com> --- .../io/opentelemetry/contrib/awsxray/XraySamplerClient.java | 2 +- dependencyManagement/build.gradle.kts | 2 +- ibm-mq-metrics/build.gradle.kts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java index 1c7958889..84dbd0144 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/XraySamplerClient.java @@ -52,7 +52,7 @@ final class XraySamplerClient { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_EMPTY) + .setDefaultPropertyInclusion(JsonInclude.Include.NON_EMPTY) // AWS APIs return timestamps as floats. .registerModule( new SimpleModule().addDeserializer(Date.class, new FloatDateDeserializer())) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 904f8fd12..4afbf4a6a 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { // (the constraints section below doesn't have this issue, and will only show up // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) - api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.19.2")) + api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.20.0")) api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.32.0")) api(enforcedPlatform("com.squareup.okhttp3:okhttp-bom:5.1.0")) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 552417048..2370f2893 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -34,7 +34,7 @@ dependencies { api("org.jetbrains:annotations:26.0.2-1") api("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1") api("org.yaml:snakeyaml:2.5") - api("com.fasterxml.jackson.core:jackson-databind:2.19.2") + api("com.fasterxml.jackson.core:jackson-databind:2.20.0") api("io.opentelemetry:opentelemetry-sdk") api("io.opentelemetry:opentelemetry-exporter-otlp") api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") From 1a19344d3b9e285febb07d0aaada81d62c72b081 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Mon, 8 Sep 2025 21:49:18 -0700 Subject: [PATCH 261/371] Simplify renovate glob patterns (#2241) --- .github/renovate.json5 | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index ca0c996d3..6a0416b69 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -85,7 +85,7 @@ ], enabled: false, matchPackageNames: [ - 'org.mockito:{/,}**', + 'org.mockito:**', ], }, { @@ -123,31 +123,33 @@ matchCurrentVersion: '3.5.0', enabled: false, matchPackageNames: [ - 'org.apache.maven:{/,}**', + 'org.apache.maven:**', ], }, { groupName: 'spotless packages', matchPackageNames: [ - 'com.diffplug.spotless{/,}**', + 'com.diffplug.spotless', + 'com.diffplug.spotless:**', ], }, { groupName: 'hipparchus packages', matchPackageNames: [ - 'org.hipparchus{/,}**', + 'org.hipparchus:**', ], }, { groupName: 'errorprone packages', matchPackageNames: [ - 'com.google.errorprone{/,}**', + 'com.google.errorprone:**', ], }, { groupName: 'jackson packages', matchPackageNames: [ - 'com.fasterxml.jackson{/,}**', + 'com.fasterxml.jackson:**', + 'com.fasterxml.jackson.core:**', ], }, { @@ -157,7 +159,7 @@ ], enabled: false, matchPackageNames: [ - 'org.openjdk.jmc{/,}**', + 'org.openjdk.jmc:**', ], }, { @@ -168,7 +170,7 @@ matchCurrentVersion: '5.0.0', enabled: false, matchPackageNames: [ - 'jakarta.servlet:{/,}**', + 'jakarta.servlet:**', ], }, { @@ -181,7 +183,7 @@ ], enabled: false, matchPackageNames: [ - 'org.springframework.boot{/,}**', + 'org.springframework.boot:**', ], }, ], From b5b5fb71c5ec292f43a85b9d816dcc2dc8d7e1c8 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 9 Sep 2025 14:53:34 +0200 Subject: [PATCH 262/371] update file format (#2244) --- .../awsxray/propagator/internal/AwsComponentProviderTest.java | 2 +- .../internal/EventToSpanBridgeComponentProviderTest.java | 2 +- .../internal/RuleBasedRoutingSamplerComponentProviderTest.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java b/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java index c2869d265..5bcd62137 100644 --- a/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java +++ b/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/internal/AwsComponentProviderTest.java @@ -21,7 +21,7 @@ class AwsComponentProviderTest { @Test void endToEnd() { String yaml = - "file_format: 0.4\n" + "file_format: 1.0-rc.1\n" + "propagator:\n" + " composite:\n" + " - xray:\n" diff --git a/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java b/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java index 55746e61d..1e503a51c 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/eventbridge/internal/EventToSpanBridgeComponentProviderTest.java @@ -18,7 +18,7 @@ class EventToSpanBridgeComponentProviderTest { @Test void endToEnd() { String yaml = - "file_format: 0.4\n" + "file_format: 1.0-rc.1\n" + "logger_provider:\n" + " processors:\n" + " - event_to_span_event_bridge:\n"; diff --git a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java b/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java index 02611c9d5..c39603352 100644 --- a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java +++ b/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java @@ -38,7 +38,7 @@ class RuleBasedRoutingSamplerComponentProviderTest { @Test void endToEnd() { String yaml = - "file_format: 0.4\n" + "file_format: 1.0-rc.1\n" + "tracer_provider:\n" + " sampler:\n" + " parent_based:\n" From bb5449f3bce1ceb6bfa2fd7cc046b615466387de Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 9 Sep 2025 17:49:13 +0200 Subject: [PATCH 263/371] add declarative config for baggage (#2031) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- baggage-processor/README.md | 30 ++++++++++++++++ baggage-processor/build.gradle.kts | 8 +++++ .../BaggageLogRecordComponentProvider.java | 34 +++++++++++++++++++ .../processor/BaggageProcessorCustomizer.java | 4 ++- .../BaggageSpanComponentProvider.java | 34 +++++++++++++++++++ ...re.spi.AutoConfigurationCustomizerProvider | 1 - ...BaggageLogRecordComponentProviderTest.java | 34 +++++++++++++++++++ .../BaggageSpanComponentProviderTest.java | 34 +++++++++++++++++++ 8 files changed, 177 insertions(+), 2 deletions(-) create mode 100644 baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProvider.java create mode 100644 baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProvider.java delete mode 100644 baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider create mode 100644 baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProviderTest.java create mode 100644 baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProviderTest.java diff --git a/baggage-processor/README.md b/baggage-processor/README.md index 44719770b..10e98910d 100644 --- a/baggage-processor/README.md +++ b/baggage-processor/README.md @@ -25,6 +25,36 @@ processors through configuration. | `otel.java.experimental.span-attributes.copy-from-baggage.include` | Add baggage entries as span attributes, e.g. `key1,key2` or `*` to add all baggage items as keys. | | `otel.java.experimental.log-attributes.copy-from-baggage.include` | Add baggage entries as log attributes, e.g. `key1,key2` or `*` to add all baggage items as keys. | +### Usage with declarative configuration + +You can configure the baggage span and log record processors using declarative YAML configuration with the OpenTelemetry SDK. + +For the tracer provider (span processor): + +```yaml +file_format: 1.0-rc.1 +tracer_provider: + processors: + - baggage: + included: [foo] + excluded: [bar] +``` + +For the logger provider (log record processor): + +```yaml +file_format: 1.0-rc.1 +logger_provider: + processors: + - baggage: + included: [foo] + excluded: [bar] +``` + +This will configure the respective processor to include baggage keys listed in `included` and +exclude those in `excluded` as explained in +[Properties which pattern matching](https://github.com/open-telemetry/opentelemetry-configuration/blob/main/CONTRIBUTING.md#properties-which-pattern-matching). + ### Usage through programmatic activation Add the span and log processor when configuring the tracer and logger providers. diff --git a/baggage-processor/build.gradle.kts b/baggage-processor/build.gradle.kts index 017158399..0ff9dd52a 100644 --- a/baggage-processor/build.gradle.kts +++ b/baggage-processor/build.gradle.kts @@ -8,11 +8,19 @@ description = "OpenTelemetry Baggage Span Processor" otelJava.moduleName.set("io.opentelemetry.contrib.baggage.processor") dependencies { + annotationProcessor("com.google.auto.service:auto-service") + compileOnly("com.google.auto.service:auto-service-annotations") api("io.opentelemetry:opentelemetry-api") api("io.opentelemetry:opentelemetry-sdk") implementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly("io.opentelemetry:opentelemetry-sdk-common") + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-incubator") + testAnnotationProcessor("com.google.auto.service:auto-service") + testCompileOnly("com.google.auto.service:auto-service-annotations") + testImplementation("io.opentelemetry:opentelemetry-sdk-common") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("org.mockito:mockito-inline") testImplementation("com.google.guava:guava") diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProvider.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProvider.java new file mode 100644 index 000000000..be40ab97c --- /dev/null +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +import com.google.auto.service.AutoService; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.internal.IncludeExcludePredicate; +import io.opentelemetry.sdk.logs.LogRecordProcessor; + +@SuppressWarnings("rawtypes") +@AutoService(ComponentProvider.class) +public class BaggageLogRecordComponentProvider implements ComponentProvider { + @Override + public String getName() { + return "baggage"; + } + + @Override + public LogRecordProcessor create(DeclarativeConfigProperties config) { + return new BaggageLogRecordProcessor( + IncludeExcludePredicate.createPatternMatching( + config.getScalarList("included", String.class), + config.getScalarList("excluded", String.class))); + } + + @Override + public Class getType() { + return LogRecordProcessor.class; + } +} diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java index e45c616fc..11f85e609 100644 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java @@ -5,6 +5,7 @@ package io.opentelemetry.contrib.baggage.processor; +import com.google.auto.service.AutoService; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; @@ -12,6 +13,7 @@ import io.opentelemetry.sdk.trace.SdkTracerProviderBuilder; import java.util.List; +@AutoService(AutoConfigurationCustomizerProvider.class) public class BaggageProcessorCustomizer implements AutoConfigurationCustomizerProvider { @Override public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { @@ -57,7 +59,7 @@ private static void addLogRecordProcessor( return; } - // need to add before the batch span processor + // need to add before the batch log processor sdkLoggerProviderBuilder.addLogRecordProcessorFirst(createBaggageLogRecordProcessor(keys)); } diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProvider.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProvider.java new file mode 100644 index 000000000..19acb6ba7 --- /dev/null +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProvider.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +import com.google.auto.service.AutoService; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.internal.IncludeExcludePredicate; +import io.opentelemetry.sdk.trace.SpanProcessor; + +@SuppressWarnings("rawtypes") +@AutoService(ComponentProvider.class) +public class BaggageSpanComponentProvider implements ComponentProvider { + @Override + public String getName() { + return "baggage"; + } + + @Override + public SpanProcessor create(DeclarativeConfigProperties config) { + return new BaggageSpanProcessor( + IncludeExcludePredicate.createPatternMatching( + config.getScalarList("included", String.class), + config.getScalarList("excluded", String.class))); + } + + @Override + public Class getType() { + return SpanProcessor.class; + } +} diff --git a/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider b/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider deleted file mode 100644 index 8eb4afb06..000000000 --- a/baggage-processor/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider +++ /dev/null @@ -1 +0,0 @@ -io.opentelemetry.contrib.baggage.processor.BaggageProcessorCustomizer diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProviderTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProviderTest.java new file mode 100644 index 000000000..1c8bd28bb --- /dev/null +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordComponentProviderTest.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +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 BaggageLogRecordComponentProviderTest { + + @Test + void declarativeConfig() { + String yaml = + "file_format: 1.0-rc.1\n" + + "logger_provider:\n" + + " processors:\n" + + " - baggage:\n" + + " included: [foo]\n" + + " excluded: [bar]\n"; + + OpenTelemetrySdk sdk = + DeclarativeConfiguration.parseAndCreate( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); + + assertThat(sdk).asString().contains("BaggageLogRecordProcessor"); + } +} diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProviderTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProviderTest.java new file mode 100644 index 000000000..77399305e --- /dev/null +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanComponentProviderTest.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.baggage.processor; + +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 BaggageSpanComponentProviderTest { + + @Test + void declarativeConfig() { + String yaml = + "file_format: 1.0-rc.1\n" + + "tracer_provider:\n" + + " processors:\n" + + " - baggage:\n" + + " included: [foo]\n" + + " excluded: [bar]\n"; + + OpenTelemetrySdk sdk = + DeclarativeConfiguration.parseAndCreate( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); + + assertThat(sdk).asString().contains("BaggageSpanProcessor"); + } +} From b6317a96ba706d10e8e70bf88eb72600f5df365b Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 9 Sep 2025 13:16:18 -0700 Subject: [PATCH 264/371] Add jaydeluca as approver (#2245) Co-authored-by: John Watson --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 29b2e01f2..4ba3fdc12 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,8 @@ For more information about the maintainer role, see the [community repository](h ### Approvers -- [John Watson](https://github.com/jkwatson), Verta.ai +- [Jay DeLuca](https://github.com/jaydeluca), Grafana +- [John Watson](https://github.com/jkwatson), Cloudera For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver). From 81974e2be5163d5f68be3c291b8fd741a90dfe06 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 10 Sep 2025 11:05:31 +0300 Subject: [PATCH 265/371] chore(deps): update all patch versions (#2246) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/build-common.yml | 8 ++++---- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 6 +++--- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 2 +- 9 files changed, 14 insertions(+), 14 deletions(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index 92f753c19..f4e171bb0 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -25,7 +25,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 with: cache-read-only: true diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index a6ecd1d86..a21ac362a 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -30,7 +30,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -49,7 +49,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -89,7 +89,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -118,7 +118,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 with: cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 558f3804f..badd5f855 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Build and publish snapshots run: ./gradlew assemble publishToSonatype diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a6eb36e71..4fb19276c 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,10 +47,10 @@ jobs: - name: Set up gradle if: matrix.language == 'java' - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Initialize CodeQL - uses: github/codeql-action/init@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 + uses: github/codeql-action/init@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 + uses: github/codeql-action/analyze@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 84a87e609..8638dc7ee 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -28,7 +28,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Build project and download dependencies run: ./gradlew build -x test diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 45e549ad0..6fbd70463 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: gradle/actions/wrapper-validation@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + - uses: gradle/actions/wrapper-validation@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 27a5edc25..fa03c4b84 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@f1f6e5f6af878fb37288ce1c627459e94dbf7d01 # v3.30.1 + uses: github/codeql-action/upload-sarif@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 with: sarif_file: results.sarif diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index 63522b47c..1494926ba 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -27,7 +27,7 @@ jobs: run: | sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - run: ./gradlew dependencyCheckAnalyze env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba27da1d4..24326e7a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Build and publish artifacts if: ${{ !inputs.already-published }} From 773eb242500a8f8d5c3d9fd212af0447b88259ae Mon Sep 17 00:00:00 2001 From: Vincent Free Date: Wed, 10 Sep 2025 17:25:48 +0200 Subject: [PATCH 266/371] [kafka-exporter] Add Kafka connectivity error handling (#2202) --- .../contrib/kafka/KafkaSpanExporter.java | 36 +++++++++++-------- .../KafkaSpanExporterIntegrationTest.java | 27 +++++++++++++- 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java index b5559832e..4b79b3a13 100644 --- a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java +++ b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java @@ -59,20 +59,28 @@ public CompletableResultCode export(@Nonnull Collection spans) { CompletableResultCode result = new CompletableResultCode(); CompletableFuture.runAsync( - () -> - producer.send( - producerRecord, - (metadata, exception) -> { - if (exception == null) { - result.succeed(); - } else { - logger.error( - String.format("Error while sending spans to Kafka topic %s", topicName), - exception); - result.fail(); - } - }), - executorService); + () -> + producer.send( + producerRecord, + (metadata, exception) -> { + if (exception == null) { + result.succeed(); + } else { + logger.error( + String.format("Error while sending spans to Kafka topic %s", topicName), + exception); + result.fail(); + } + }), + executorService) + .whenComplete( + (ignore, exception) -> { + if (exception != null) { + logger.error( + "Executor task failed while sending to Kafka topic {}", topicName, exception); + result.fail(); + } + }); return result; } diff --git a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterIntegrationTest.java b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterIntegrationTest.java index 4514efdcf..f8c010f89 100644 --- a/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterIntegrationTest.java +++ b/kafka-exporter/src/test/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterIntegrationTest.java @@ -19,6 +19,7 @@ import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.SpanData; import java.time.Duration; +import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -29,7 +30,9 @@ import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.KafkaConsumer; +import org.apache.kafka.clients.producer.MockProducer; import org.apache.kafka.clients.producer.ProducerConfig; +import org.apache.kafka.common.KafkaException; import org.apache.kafka.common.errors.ApiException; import org.apache.kafka.common.serialization.StringDeserializer; import org.apache.kafka.common.serialization.StringSerializer; @@ -46,7 +49,7 @@ @TestInstance(TestInstance.Lifecycle.PER_CLASS) class KafkaSpanExporterIntegrationTest { private static final DockerImageName KAFKA_TEST_IMAGE = - DockerImageName.parse("apache/kafka:3.8.1"); + DockerImageName.parse("apache/kafka:3.9.1"); private static final String TOPIC = "span_topic"; private KafkaContainer kafka; private KafkaConsumer consumer; @@ -155,6 +158,28 @@ void exportWhenProducerInError() { testSubject.shutdown(); } + @Test + void exportWhenProducerFailsToSend() { + var mockProducer = new MockProducer>(); + mockProducer.sendException = new KafkaException("Simulated kafka exception"); + var testSubjectWithMockProducer = + KafkaSpanExporter.newBuilder().setTopicName(TOPIC).setProducer(mockProducer).build(); + + ImmutableList spans = + ImmutableList.of(makeBasicSpan("span-1"), makeBasicSpan("span-2")); + + CompletableResultCode actual = testSubjectWithMockProducer.export(spans); + + await() + .untilAsserted( + () -> { + assertThat(actual.isSuccess()).isFalse(); + assertThat(actual.isDone()).isTrue(); + }); + + testSubjectWithMockProducer.shutdown(); + } + @Test void flush() { CompletableResultCode actual = testSubject.flush(); From 31283a32984e78b869465e76ed910f248ca208cd Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 10:00:52 -0700 Subject: [PATCH 267/371] Fix some links (#2220) --- CHANGELOG.md | 2 +- gcp-resources/README.md | 2 +- jmx-metrics/docs/target-systems/jetty.md | 2 +- jmx-scraper/README.md | 2 +- maven-extension/README.md | 8 ++++---- samplers/README.md | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07f71af44..1f34222ca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -270,7 +270,7 @@ The extension takes care of the necessary configuration required to authenticate The future of the [JMX metrics](./jmx-metrics/README.md) component, built on top of the -[JMX metrics](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jmx-metrics/javaagent#jmx-metric-insight) +[JMX metrics](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jmx-metrics#jmx-metric-insight) component from the opentelemetry-java-instrumentation repository. ### Maven extension diff --git a/gcp-resources/README.md b/gcp-resources/README.md index 6a80e1baf..9f670e088 100644 --- a/gcp-resources/README.md +++ b/gcp-resources/README.md @@ -47,7 +47,7 @@ env: ## Usage with Manual Instrumentation -It is recommended to use this resource detector with the [OpenTelemetry Autoconfiguration SPI](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/autoconfigure/README.md#resource-provider-spi). The GCP resource detector automatically provides the detected resources via the [autoconfigure-spi](https://github.com/open-telemetry/opentelemetry-java/tree/main/sdk-extensions/autoconfigure-spi) SDK extension. +It is recommended to use this resource detector with the [OpenTelemetry SDK autoconfiguration](https://opentelemetry.io/docs/languages/java/configuration/#zero-code-sdk-autoconfigure). The GCP resource detector automatically provides the detected resources via the [autoconfigure-spi](https://opentelemetry.io/docs/languages/java/configuration/#spi-service-provider-interface) SDK extension. For a reference example showcasing the detected resource attributes and usage with `autoconfigure-spi`, see the [Resource detection example](https://github.com/open-telemetry/opentelemetry-java-examples/tree/main/resource-detection-gcp). diff --git a/jmx-metrics/docs/target-systems/jetty.md b/jmx-metrics/docs/target-systems/jetty.md index 46724daf3..eb74f4f51 100644 --- a/jmx-metrics/docs/target-systems/jetty.md +++ b/jmx-metrics/docs/target-systems/jetty.md @@ -1,7 +1,7 @@ # Jetty Metrics The JMX Metric Gatherer provides built in Jetty metric gathering capabilities. -Details about using JMX with WildFly can be found here: https://www.eclipse.org/jetty/documentation/jetty-11/operations-guide/index.html#og-jmx +Details about using JMX with WildFly can be found here: https://jetty.org/docs/jetty/11/operations-guide/jmx/index.html ### Metrics * Name: `jetty.select.count` diff --git a/jmx-scraper/README.md b/jmx-scraper/README.md index 5a3b496a2..a69736e15 100644 --- a/jmx-scraper/README.md +++ b/jmx-scraper/README.md @@ -12,7 +12,7 @@ This project is released as part of the [OpenTelemetry Java Contrib](https://git The latest release is available from: - [GitHub Release assets](https://github.com/open-telemetry/opentelemetry-java-contrib/releases/latest/download/opentelemetry-jmx-scraper.jar) -- [Maven Central](https://central.sonatype.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper) or can be browsed via [MVN Repository](https://mvnrepository.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper). +- [Maven Central](https://central.sonatype.com/artifact/io.opentelemetry.contrib/opentelemetry-jmx-scraper) ## Usage diff --git a/maven-extension/README.md b/maven-extension/README.md index 91ec71744..fd0150355 100644 --- a/maven-extension/README.md +++ b/maven-extension/README.md @@ -12,7 +12,7 @@ The Maven OpenTelemetry Extension is configured using environment variables or J * (since Maven 3.3.1) configuring the extension in `.mvn/extensions.xml`. In the code snippets below, replace `OPENTELEMETRY_MAVEN_VERSION` with the [latest -release](https://search.maven.org/search?q=g:io.opentelemetry.contrib%20AND%20a:opentelemetry-maven-extension). +release](https://central.sonatype.com/artifact/io.opentelemetry.contrib/opentelemetry-maven-extension). ### Adding the extension to the classpath @@ -109,8 +109,8 @@ In addition to the span attributes captured on every Maven plugin goal executio |----------------------------------------|--------|-----------------------------------------------------------------------------------------------------------------------------------------------------| | `http.method` | string | `POST` | | `http.url` | string | Base URL of the uploaded artifact `${maven.build.repository.url}/${groupId}/${artifactId}/${version}` where the `.` of `${groupId}` are replaced by `/` | -| `maven.build.repository.id` | string | ID of the Maven repository to which the artifact is deployed. See [Maven POM reference / Repository](https://maven.apache.org/pom.html#repository) | -| `maven.build.repository.url` | string | URL of the Maven repository to which the artifact is deployed. See [Maven POM reference / Repository](https://maven.apache.org/pom.html#repository) | +| `maven.build.repository.id` | string | ID of the Maven repository to which the artifact is deployed. See [Maven POM reference / Repository](https://maven.apache.org/pom.html#Repository) | +| `maven.build.repository.url` | string | URL of the Maven repository to which the artifact is deployed. See [Maven POM reference / Repository](https://maven.apache.org/pom.html#Repository) | | `peer.service` | string | Maven repository hostname deduced from the Repository URL | The `span.kind` is set to `client` @@ -200,7 +200,7 @@ Steps to instrument a Maven Mojo: * Add the OpenTelemetry API dependency in the `pom.xml` of the Maven plugin. Replace `OPENTELEMETRY_VERSION` with the [latest - release](https://search.maven.org/search?q=g:io.opentelemetry%20AND%20a:opentelemetry-api). + release](https://central.sonatype.com/artifact/io.opentelemetry/opentelemetry-api). ```xml diff --git a/samplers/README.md b/samplers/README.md index 854d44e61..aee9dee5f 100644 --- a/samplers/README.md +++ b/samplers/README.md @@ -9,7 +9,7 @@ The following samplers support [declarative configuration](https://opentelemetry To use: * Add a dependency on `io.opentelemetry.contrib:opentelemetry-samplers:` -* Follow the [instructions](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/README.md#file-configuration) to configure OpenTelemetry with declarative configuration. +* Follow the [instructions](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/README.md#declarative-configuration) to configure OpenTelemetry with declarative configuration. * Configure the `.tracer_provider.sampler` to include the `rule_based_routing` sampler. NOTE: Not yet available for use with the OTEL java agent, but should be in the near future. Please check back for updates. From b3aa68c5a472174086199c65d8845f9a1635f146 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 10:42:00 -0700 Subject: [PATCH 268/371] Apply style guide section for JUnit (#2212) --- .../contrib/aws/resource/EksResourceTest.java | 2 +- .../AwsXrayCompositePropagatorTest.java | 2 +- ...AttributePropagatingSpanProcessorTest.java | 10 +++--- .../AwsMetricAttributeGeneratorTest.java | 32 +++++++++---------- .../AwsMetricAttributesSpanExporterTest.java | 16 +++++----- .../awsxray/AwsSpanMetricsProcessorTest.java | 18 +++++------ .../contrib/awsxray/ResourceHolderTest.java | 4 +-- .../MetadataBasedResourceProviderTest.java | 12 +++---- .../BaggageProcessorCustomizerTest.java | 8 ++--- .../processor/BaggageSpanProcessorTest.java | 8 ++--- ...ConsistentProbabilityBasedSamplerTest.java | 4 +-- .../consistent/OtelTraceStateTest.java | 4 +-- .../consistent/RandomGeneratorTest.java | 2 +- .../ConsistentAlwaysOffSamplerTest.java | 2 +- .../ConsistentAlwaysOnSamplerTest.java | 2 +- .../ConsistentFixedThresholdSamplerTest.java | 6 ++-- .../ConsistentSamplingUtilTest.java | 2 +- .../consistent56/OtelTraceStateTest.java | 4 +-- .../RandomValueGeneratorsTest.java | 2 +- .../disk/buffering/IntegrationTest.java | 2 +- ...toConfigurationCustomizerProviderTest.java | 14 ++++---- .../auth/GcpAuthExtensionEndToEndTest.java | 4 +-- .../gcp/resource/GCPResourceProviderTest.java | 22 ++++++------- .../InquireChannelCmdCollectorTest.java | 2 +- .../ListenerMetricsCollectorTest.java | 2 +- .../QueueCollectionBuddyTest.java | 2 +- .../QueueManagerMetricsCollectorTest.java | 2 +- .../TopicMetricsCollectorTest.java | 2 +- .../InferredSpansAutoConfigTest.java | 6 ++-- .../internal/SamplingProfilerQueueTest.java | 2 +- .../contrib/jmxscraper/JmxConnectionTest.java | 2 +- .../ActiveMqIntegrationTest.java | 2 +- .../CassandraIntegrationTest.java | 2 +- .../target_systems/CustomIntegrationTest.java | 2 +- .../target_systems/HBaseIntegrationTest.java | 2 +- .../target_systems/HadoopIntegrationTest.java | 2 +- .../target_systems/JettyIntegrationTest.java | 2 +- .../target_systems/JvmIntegrationTest.java | 2 +- .../target_systems/SolrIntegrationTest.java | 2 +- .../target_systems/TomcatIntegrationTest.java | 2 +- .../WildflyIntegrationTest.java | 2 +- .../kafka/KafkaConsumerIntegrationTest.java | 2 +- .../kafka/KafkaIntegrationTest.java | 2 +- .../kafka/KafkaProducerIntegrationTest.java | 2 +- .../maven/OpenTelemetrySdkServiceTest.java | 12 +++---- .../opentelemetry/maven/SpanRegistryTest.java | 4 +-- ...GoalExecutionHandlerConfigurationTest.java | 5 ++- .../handler/MojoGoalExecutionHandlerTest.java | 16 +++++----- .../micrometer/PrometheusIntegrationTest.java | 2 +- .../MicrometerMeterProviderTest.java | 2 +- .../micrometer/MicrometerMeterTest.java | 2 +- .../MicrometerDoubleCounterTest.java | 2 +- .../MicrometerDoubleGaugeTest.java | 2 +- .../MicrometerDoubleHistogramTest.java | 2 +- .../MicrometerDoubleUpDownCounterTest.java | 2 +- .../MicrometerLongCounterTest.java | 2 +- .../instruments/MicrometerLongGaugeTest.java | 2 +- .../MicrometerLongHistogramTest.java | 2 +- .../MicrometerLongUpDownCounterTest.java | 2 +- .../FilteringLogRecordProcessorTest.java | 2 +- .../JettyServiceNameDetectorTest.java | 2 +- .../attach/AbstractAttachmentTest.java | 2 +- ...gentDisabledByEnvironmentVariableTest.java | 2 +- .../AgentDisabledBySystemPropertyTest.java | 2 +- .../attach/RunTimeAttachBasicTest.java | 2 +- .../sampler/RuleBasedRoutingSamplerTest.java | 14 ++++---- .../stacktrace/StackTraceAutoConfigTest.java | 2 +- 67 files changed, 157 insertions(+), 158 deletions(-) diff --git a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/EksResourceTest.java b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/EksResourceTest.java index 7eaec5e55..31b05be57 100644 --- a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/EksResourceTest.java +++ b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/EksResourceTest.java @@ -34,7 +34,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class EksResourceTest { +class EksResourceTest { @Mock private DockerHelper mockDockerHelper; diff --git a/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayCompositePropagatorTest.java b/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayCompositePropagatorTest.java index 53e806fdc..99998d974 100644 --- a/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayCompositePropagatorTest.java +++ b/aws-xray-propagator/src/test/java/io/opentelemetry/contrib/awsxray/propagator/AwsXrayCompositePropagatorTest.java @@ -15,7 +15,7 @@ import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; -public class AwsXrayCompositePropagatorTest extends AwsXrayPropagatorTest { +class AwsXrayCompositePropagatorTest extends AwsXrayPropagatorTest { @Override TextMapPropagator propagator() { diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AttributePropagatingSpanProcessorTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AttributePropagatingSpanProcessorTest.java index b4f40e408..0bf394e0e 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AttributePropagatingSpanProcessorTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AttributePropagatingSpanProcessorTest.java @@ -41,7 +41,7 @@ public void setup() { } @Test - public void testAttributesPropagation() { + void testAttributesPropagation() { Span spanWithAppOnly = tracer.spanBuilder("parent").startSpan(); spanWithAppOnly.setAttribute(testKey1, "testValue1"); validateSpanAttributesInheritance(spanWithAppOnly, null, "testValue1", null); @@ -57,7 +57,7 @@ public void testAttributesPropagation() { } @Test - public void testOverrideAttributes() { + void testOverrideAttributes() { Span parentSpan = tracer.spanBuilder("parent").startSpan(); parentSpan.setAttribute(testKey1, "testValue1"); parentSpan.setAttribute(testKey2, "testValue2"); @@ -75,13 +75,13 @@ public void testOverrideAttributes() { } @Test - public void testAttributesDoNotExist() { + void testAttributesDoNotExist() { Span span = tracer.spanBuilder("parent").startSpan(); validateSpanAttributesInheritance(span, null, null, null); } @Test - public void testSpanNamePropagationBySpanKind() { + void testSpanNamePropagationBySpanKind() { for (SpanKind value : SpanKind.values()) { Span span = tracer.spanBuilder("parent").setSpanKind(value).startSpan(); if (value == SpanKind.SERVER || value == SpanKind.CONSUMER) { @@ -93,7 +93,7 @@ public void testSpanNamePropagationBySpanKind() { } @Test - public void testSpanNamePropagationWithRemoteParentSpan() { + void testSpanNamePropagationWithRemoteParentSpan() { Span remoteParent = Span.wrap( SpanContext.createFromRemoteParent( diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java index 135a1eeff..4d38e89ff 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributeGeneratorTest.java @@ -79,7 +79,7 @@ public void setUpMocks() { } @Test - public void testConsumerSpanWithoutAttributes() { + void testConsumerSpanWithoutAttributes() { Attributes expectedAttributes = Attributes.of( AWS_SPAN_KIND, SpanKind.CONSUMER.name(), @@ -89,7 +89,7 @@ public void testConsumerSpanWithoutAttributes() { } @Test - public void testServerSpanWithoutAttributes() { + void testServerSpanWithoutAttributes() { Attributes expectedAttributes = Attributes.of( AWS_SPAN_KIND, SpanKind.SERVER.name(), @@ -99,7 +99,7 @@ public void testServerSpanWithoutAttributes() { } @Test - public void testProducerSpanWithoutAttributes() { + void testProducerSpanWithoutAttributes() { Attributes expectedAttributes = Attributes.of( AWS_SPAN_KIND, SpanKind.PRODUCER.name(), @@ -111,7 +111,7 @@ public void testProducerSpanWithoutAttributes() { } @Test - public void testClientSpanWithoutAttributes() { + void testClientSpanWithoutAttributes() { Attributes expectedAttributes = Attributes.of( AWS_SPAN_KIND, SpanKind.CLIENT.name(), @@ -123,13 +123,13 @@ public void testClientSpanWithoutAttributes() { } @Test - public void testInternalSpan() { + void testInternalSpan() { // Spans with internal span kind should not produce any attributes. validateAttributesProducedForSpanOfKind(Attributes.empty(), SpanKind.INTERNAL); } @Test - public void testConsumerSpanWithAttributes() { + void testConsumerSpanWithAttributes() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn(SPAN_NAME_VALUE); @@ -142,7 +142,7 @@ public void testConsumerSpanWithAttributes() { } @Test - public void testServerSpanWithAttributes() { + void testServerSpanWithAttributes() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn(SPAN_NAME_VALUE); @@ -155,7 +155,7 @@ public void testServerSpanWithAttributes() { } @Test - public void testServerSpanWithNullSpanName() { + void testServerSpanWithNullSpanName() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn(null); @@ -168,7 +168,7 @@ public void testServerSpanWithNullSpanName() { } @Test - public void testServerSpanWithSpanNameAsHttpMethod() { + void testServerSpanWithSpanNameAsHttpMethod() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn("GET"); mockAttribute(HTTP_METHOD, "GET"); @@ -183,7 +183,7 @@ public void testServerSpanWithSpanNameAsHttpMethod() { } @Test - public void testServerSpanWithSpanNameWithHttpTarget() { + void testServerSpanWithSpanNameWithHttpTarget() { updateResourceWithServiceName(); when(spanDataMock.getName()).thenReturn("POST"); mockAttribute(HTTP_METHOD, "POST"); @@ -203,7 +203,7 @@ public void testServerSpanWithSpanNameWithHttpTarget() { } @Test - public void testProducerSpanWithAttributes() { + void testProducerSpanWithAttributes() { updateResourceWithServiceName(); mockAttribute(AWS_LOCAL_OPERATION, AWS_LOCAL_OPERATION_VALUE); mockAttribute(AWS_REMOTE_SERVICE, AWS_REMOTE_SERVICE_VALUE); @@ -220,7 +220,7 @@ public void testProducerSpanWithAttributes() { } @Test - public void testClientSpanWithAttributes() { + void testClientSpanWithAttributes() { updateResourceWithServiceName(); mockAttribute(AWS_LOCAL_OPERATION, AWS_LOCAL_OPERATION_VALUE); mockAttribute(AWS_REMOTE_SERVICE, AWS_REMOTE_SERVICE_VALUE); @@ -237,7 +237,7 @@ public void testClientSpanWithAttributes() { } @Test - public void testRemoteAttributesCombinations() { + void testRemoteAttributesCombinations() { // Set all expected fields to a test string, we will overwrite them in descending order to test // the priority-order logic in AwsMetricAttributeGenerator remote attribute methods. mockAttribute(AWS_REMOTE_SERVICE, "TestString"); @@ -333,7 +333,7 @@ public void testRemoteAttributesCombinations() { } @Test - public void testPeerServiceDoesOverrideOtherRemoteServices() { + void testPeerServiceDoesOverrideOtherRemoteServices() { validatePeerServiceDoesOverride(RPC_SERVICE); validatePeerServiceDoesOverride(DB_SYSTEM); validatePeerServiceDoesOverride(FAAS_INVOKED_PROVIDER); @@ -346,7 +346,7 @@ public void testPeerServiceDoesOverrideOtherRemoteServices() { } @Test - public void testPeerServiceDoesNotOverrideAwsRemoteService() { + void testPeerServiceDoesNotOverrideAwsRemoteService() { mockAttribute(AWS_REMOTE_SERVICE, "TestString"); mockAttribute(PEER_SERVICE, "PeerService"); @@ -357,7 +357,7 @@ public void testPeerServiceDoesNotOverrideAwsRemoteService() { } @Test - public void testClientSpanWithRemoteTargetAttributes() { + void testClientSpanWithRemoteTargetAttributes() { // Validate behaviour of aws bucket name attribute, then remove it. mockAttribute(AWS_BUCKET_NAME, "aws_s3_bucket_name"); validateRemoteTargetAttributes(AWS_REMOTE_TARGET, "aws_s3_bucket_name"); diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributesSpanExporterTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributesSpanExporterTest.java index aedf5fa06..8502734d7 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributesSpanExporterTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsMetricAttributesSpanExporterTest.java @@ -62,7 +62,7 @@ public void setUpMocks() { } @Test - public void testPassthroughDelegations() { + void testPassthroughDelegations() { awsMetricAttributesSpanExporter.flush(); awsMetricAttributesSpanExporter.shutdown(); awsMetricAttributesSpanExporter.close(); @@ -72,7 +72,7 @@ public void testPassthroughDelegations() { } @Test - public void testExportDelegationWithoutAttributeOrModification() { + void testExportDelegationWithoutAttributeOrModification() { Attributes spanAttributes = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES); SpanData spanDataMock = buildSpanDataMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES); @@ -88,7 +88,7 @@ public void testExportDelegationWithoutAttributeOrModification() { } @Test - public void testExportDelegationWithAttributeButWithoutModification() { + void testExportDelegationWithAttributeButWithoutModification() { Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES); SpanData spanDataMock = buildSpanDataMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES); @@ -104,7 +104,7 @@ public void testExportDelegationWithAttributeButWithoutModification() { } @Test - public void testExportDelegationWithoutAttributeButWithModification() { + void testExportDelegationWithoutAttributeButWithModification() { Attributes spanAttributes = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES); SpanData spanDataMock = buildSpanDataMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); @@ -124,7 +124,7 @@ public void testExportDelegationWithoutAttributeButWithModification() { } @Test - public void testExportDelegationWithAttributeAndModification() { + void testExportDelegationWithAttributeAndModification() { Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES); SpanData spanDataMock = buildSpanDataMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); @@ -146,7 +146,7 @@ public void testExportDelegationWithAttributeAndModification() { } @Test - public void testExportDelegationWithMultipleSpans() { + void testExportDelegationWithMultipleSpans() { Attributes spanAttributes1 = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES); SpanData spanDataMock1 = buildSpanDataMock(spanAttributes1); Attributes metricAttributes1 = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES); @@ -185,7 +185,7 @@ public void testExportDelegationWithMultipleSpans() { } @Test - public void testOverridenAttributes() { + void testOverridenAttributes() { Attributes spanAttributes = Attributes.of( AttributeKey.stringKey("key1"), @@ -217,7 +217,7 @@ public void testOverridenAttributes() { } @Test - public void testExportDelegatingSpanDataBehaviour() { + void testExportDelegatingSpanDataBehaviour() { Attributes spanAttributes = buildSpanAttributes(CONTAINS_ATTRIBUTES); SpanData spanDataMock = buildSpanDataMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java index 0836f5a8e..5d10a6a3d 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessorTest.java @@ -101,13 +101,13 @@ public void setUpMocks() { } @Test - public void testIsRequired() { + void testIsRequired() { assertThat(awsSpanMetricsProcessor.isStartRequired()).isFalse(); assertThat(awsSpanMetricsProcessor.isEndRequired()).isTrue(); } @Test - public void testStartDoesNothingToSpan() { + void testStartDoesNothingToSpan() { Context parentContextMock = mock(Context.class); ReadWriteSpan spanMock = mock(ReadWriteSpan.class); awsSpanMetricsProcessor.onStart(parentContextMock, spanMock); @@ -115,7 +115,7 @@ public void testStartDoesNothingToSpan() { } @Test - public void testTearDown() { + void testTearDown() { assertThat(awsSpanMetricsProcessor.shutdown()).isEqualTo(CompletableResultCode.ofSuccess()); assertThat(awsSpanMetricsProcessor.forceFlush()).isEqualTo(CompletableResultCode.ofSuccess()); @@ -128,7 +128,7 @@ public void testTearDown() { * AwsSpanMetricsProcessor's onEnd method pertaining to metrics generation. */ @Test - public void testOnEndMetricsGenerationWithoutSpanAttributes() { + void testOnEndMetricsGenerationWithoutSpanAttributes() { Attributes spanAttributes = buildSpanAttributes(CONTAINS_NO_ATTRIBUTES); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); @@ -141,7 +141,7 @@ public void testOnEndMetricsGenerationWithoutSpanAttributes() { } @Test - public void testOnEndMetricsGenerationWithoutMetricAttributes() { + void testOnEndMetricsGenerationWithoutMetricAttributes() { Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, 500L); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_NO_ATTRIBUTES); @@ -154,7 +154,7 @@ public void testOnEndMetricsGenerationWithoutMetricAttributes() { } @Test - public void testOnEndMetricsGenerationWithoutEndRequired() { + void testOnEndMetricsGenerationWithoutEndRequired() { Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, 500L); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); @@ -167,7 +167,7 @@ public void testOnEndMetricsGenerationWithoutEndRequired() { } @Test - public void testOnEndMetricsGenerationWithLatency() { + void testOnEndMetricsGenerationWithLatency() { Attributes spanAttributes = Attributes.of(HTTP_STATUS_CODE, 200L); ReadableSpan readableSpanMock = buildReadableSpanMock(spanAttributes); Attributes metricAttributes = buildMetricAttributes(CONTAINS_ATTRIBUTES); @@ -182,7 +182,7 @@ public void testOnEndMetricsGenerationWithLatency() { } @Test - public void testOnEndMetricsGenerationWithAwsStatusCodes() { + void testOnEndMetricsGenerationWithAwsStatusCodes() { validateMetricsGeneratedForAwsStatusCode(399L, ExpectedStatusMetric.NEITHER); validateMetricsGeneratedForAwsStatusCode(400L, ExpectedStatusMetric.ERROR); validateMetricsGeneratedForAwsStatusCode(499L, ExpectedStatusMetric.ERROR); @@ -192,7 +192,7 @@ public void testOnEndMetricsGenerationWithAwsStatusCodes() { } @Test - public void testOnEndMetricsGenerationWithStatusCodes() { + void testOnEndMetricsGenerationWithStatusCodes() { // Invalid HTTP status codes validateMetricsGeneratedForHttpStatusCode(null, ExpectedStatusMetric.NEITHER); diff --git a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/ResourceHolderTest.java b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/ResourceHolderTest.java index 1140abc5f..f81f3d370 100644 --- a/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/ResourceHolderTest.java +++ b/aws-xray/src/test/java/io/opentelemetry/contrib/awsxray/ResourceHolderTest.java @@ -21,11 +21,11 @@ * Unit tests for {@link ResourceHolder}. Note that there isn't a great way to test the "default" * fallback logic, as when the test suite is run, the customize logic appears to be invoked. */ -public class ResourceHolderTest { +class ResourceHolderTest { @Test @SuppressWarnings("unchecked") - public void testCustomized() { + void testCustomized() { Resource customizedResource = Resource.create(Attributes.empty()); AutoConfigurationCustomizer mockCustomizer = mock(AutoConfigurationCustomizer.class); ResourceHolder resourceHolder = new ResourceHolder(); diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java index 25d9e065c..3d65c1e0a 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/MetadataBasedResourceProviderTest.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; -public abstract class MetadataBasedResourceProviderTest { +abstract class MetadataBasedResourceProviderTest { @RegisterExtension public static final MockWebServerExtension server = new MockWebServerExtension(); @@ -79,30 +79,30 @@ protected static String okResponse() { } @Test - public void successFromFile() { + void successFromFile() { assertDefaultAttributes(createResource(() -> Optional.of(okResponse()))); } @Test - public void successFromMockServer() { + void successFromMockServer() { server.enqueue(HttpResponse.of(MediaType.JSON, okResponse())); assertDefaultAttributes(mockServerResponse()); } @Test - public void responseNotFound() { + void responseNotFound() { server.enqueue(HttpResponse.of(HttpStatus.NOT_FOUND)); mockServerResponse().isEmpty(); } @Test - public void responseEmpty() { + void responseEmpty() { server.enqueue(HttpResponse.of("")); assertOnlyProvider(mockServerResponse()); } @Test - public void responseEmptyJson() { + void responseEmptyJson() { server.enqueue(HttpResponse.of("{}")); assertOnlyProvider(mockServerResponse()); } diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java index 6d2e77469..645ff5334 100644 --- a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizerTest.java @@ -159,7 +159,7 @@ public String getName() { } @Test - public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSpan span) { + void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSpan span) { try (BaggageSpanProcessor processor = BaggageProcessorCustomizer.createBaggageSpanProcessor(Collections.singletonList("*"))) { try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { @@ -170,7 +170,7 @@ public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSp } @Test - public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches( + void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches( @Mock ReadWriteSpan span) { try (BaggageSpanProcessor processor = BaggageProcessorCustomizer.createBaggageSpanProcessor(Collections.singletonList("key"))) { @@ -188,7 +188,7 @@ public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_m } @Test - public void test_baggageLogRecordProcessor_adds_attributes_to_logRecord( + void test_baggageLogRecordProcessor_adds_attributes_to_logRecord( @Mock ReadWriteLogRecord logRecord) { try (BaggageLogRecordProcessor processor = BaggageProcessorCustomizer.createBaggageLogRecordProcessor( @@ -201,7 +201,7 @@ public void test_baggageLogRecordProcessor_adds_attributes_to_logRecord( } @Test - public void test_baggageLogRecordProcessor_adds_attributes_to_spans_when_key_filter_matches( + void test_baggageLogRecordProcessor_adds_attributes_to_spans_when_key_filter_matches( @Mock ReadWriteLogRecord logRecord) { try (BaggageLogRecordProcessor processor = BaggageProcessorCustomizer.createBaggageLogRecordProcessor( diff --git a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java index ca1180dcc..dd7ea3826 100644 --- a/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java +++ b/baggage-processor/src/test/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessorTest.java @@ -17,10 +17,10 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class BaggageSpanProcessorTest { +class BaggageSpanProcessorTest { @Test - public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSpan span) { + void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSpan span) { try (BaggageSpanProcessor processor = BaggageSpanProcessor.allowAllBaggageKeys()) { try (Scope ignore = Baggage.current().toBuilder().put("key", "value").build().makeCurrent()) { processor.onStart(Context.current(), span); @@ -30,7 +30,7 @@ public void test_baggageSpanProcessor_adds_attributes_to_spans(@Mock ReadWriteSp } @Test - public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches( + void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches( @Mock ReadWriteSpan span) { try (BaggageSpanProcessor processor = new BaggageSpanProcessor(key -> key.startsWith("k"))) { try (Scope ignore = @@ -47,7 +47,7 @@ public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_m } @Test - public void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches_regex( + void test_baggageSpanProcessor_adds_attributes_to_spans_when_key_filter_matches_regex( @Mock ReadWriteSpan span) { Pattern pattern = Pattern.compile("k.*"); try (BaggageSpanProcessor processor = diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java index 4b9d3e425..d8cbe3a16 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class ConsistentProbabilityBasedSamplerTest { +class ConsistentProbabilityBasedSamplerTest { private Context parentContext; private String traceId; @@ -72,7 +72,7 @@ private void test(SplittableRandom rng, double samplingProbability) { } @Test - public void test() { + void test() { // fix seed to get reproducible results SplittableRandom random = new SplittableRandom(0); diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java index a6fd85d47..39bbf5dd9 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java @@ -12,14 +12,14 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -public class OtelTraceStateTest { +class OtelTraceStateTest { private static String getXString(int len) { return Stream.generate(() -> "X").limit(len).collect(Collectors.joining()); } @Test - public void test() { + void test() { Assertions.assertEquals("", OtelTraceState.parse("").serialize()); assertEquals("", OtelTraceState.parse("").serialize()); diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/RandomGeneratorTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/RandomGeneratorTest.java index f94e7eef4..e2f336727 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/RandomGeneratorTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/RandomGeneratorTest.java @@ -14,7 +14,7 @@ import org.hipparchus.stat.inference.GTest; import org.junit.jupiter.api.Test; -public class RandomGeneratorTest { +class RandomGeneratorTest { private static void testGenerateRandomBitSet(long seed, int numBits, int numOneBits) { diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java index 9b5fc050b..d0425aa0b 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOffSamplerTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -public class ConsistentAlwaysOffSamplerTest { +class ConsistentAlwaysOffSamplerTest { @Test void testDescription() { diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java index 3a6b8531b..115c39c41 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentAlwaysOnSamplerTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -public class ConsistentAlwaysOnSamplerTest { +class ConsistentAlwaysOnSamplerTest { @Test void testDescription() { diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java index 7eac3ffb1..3d78de81a 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentFixedThresholdSamplerTest.java @@ -25,7 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class ConsistentFixedThresholdSamplerTest { +class ConsistentFixedThresholdSamplerTest { private Context parentContext; private String name; @@ -75,7 +75,7 @@ private void testSampling(SplittableRandom rng, double samplingProbability) { } @Test - public void testSampling() { + void testSampling() { // fix seed to get reproducible results SplittableRandom random = new SplittableRandom(0); @@ -92,7 +92,7 @@ public void testSampling() { } @Test - public void testDescription() { + void testDescription() { assertThat(ConsistentSampler.probabilityBased(1.0).getDescription()) .isEqualTo("ConsistentFixedThresholdSampler{threshold=0, sampling probability=1.0}"); assertThat(ConsistentSampler.probabilityBased(0.5).getDescription()) diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java index fcf2dcd8d..d612f9e0a 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/ConsistentSamplingUtilTest.java @@ -19,7 +19,7 @@ import org.junit.jupiter.api.Test; -public class ConsistentSamplingUtilTest { +class ConsistentSamplingUtilTest { @Test void testCalculateSamplingProbability() { diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java index a131e9b78..7d306d959 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java @@ -11,14 +11,14 @@ import java.util.stream.Stream; import org.junit.jupiter.api.Test; -public class OtelTraceStateTest { +class OtelTraceStateTest { private static String getXString(int len) { return Stream.generate(() -> "X").limit(len).collect(Collectors.joining()); } @Test - public void test() { + void test() { assertEquals("", OtelTraceState.parse("").serialize()); assertEquals("", OtelTraceState.parse("").serialize()); diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java index ab7d378b6..d9a34255f 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/RandomValueGeneratorsTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.Test; -public class RandomValueGeneratorsTest { +class RandomValueGeneratorsTest { @Test void testRandomRange() { int attempts = 10000; diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java index b46cba12f..68afb3cbf 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java @@ -53,7 +53,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -public class IntegrationTest { +class IntegrationTest { private InMemorySpanExporter memorySpanExporter; private Tracer tracer; private InMemoryMetricExporter memoryMetricExporter; diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index be0dbe7b2..a4e7712c1 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -130,7 +130,7 @@ public void setup() { // TODO: Use parameterized test for testing traces customizer for http & grpc. @Test - public void testTraceCustomizerOtlpHttp() { + void testTraceCustomizerOtlpHttp() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -192,7 +192,7 @@ public void testTraceCustomizerOtlpHttp() { } @Test - public void testTraceCustomizerOtlpGrpc() { + void testTraceCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -246,7 +246,7 @@ public void testTraceCustomizerOtlpGrpc() { // TODO: Use parameterized test for testing metrics customizer for http & grpc. @Test - public void testMetricCustomizerOtlpHttp() { + void testMetricCustomizerOtlpHttp() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -307,7 +307,7 @@ public void testMetricCustomizerOtlpHttp() { } @Test - public void testMetricCustomizerOtlpGrpc() { + void testMetricCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -368,7 +368,7 @@ public void testMetricCustomizerOtlpGrpc() { } @Test - public void testCustomizerFailWithMissingResourceProject() { + void testCustomizerFailWithMissingResourceProject() { System.setProperty( ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); @@ -387,7 +387,7 @@ public void testCustomizerFailWithMissingResourceProject() { @ParameterizedTest @MethodSource("provideQuotaBehaviorTestCases") @SuppressWarnings("CannotMockMethod") - public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws IOException { + void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws IOException { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -461,7 +461,7 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws @ParameterizedTest @MethodSource("provideTargetSignalBehaviorTestCases") - public void testTargetSignalsBehavior(TargetSignalBehavior testCase) { + void testTargetSignalsBehavior(TargetSignalBehavior testCase) { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java index e04baed93..0553007b8 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java @@ -51,7 +51,7 @@ @SpringBootTest( classes = {Application.class}, webEnvironment = WebEnvironment.RANDOM_PORT) -public class GcpAuthExtensionEndToEndTest { +class GcpAuthExtensionEndToEndTest { @LocalServerPort private int testApplicationPort; // port at which the spring app is running @@ -115,7 +115,7 @@ public static void teardown() { } @Test - public void authExtensionSmokeTest() { + void authExtensionSmokeTest() { template.getForEntity( URI.create("http://localhost:" + testApplicationPort + "/ping"), String.class); diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java index b0683994e..9314a1b67 100644 --- a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java @@ -186,7 +186,7 @@ private static DetectedPlatform generateMockUnknownPlatform() { } @Test - public void testGceResourceAttributesMapping() { + void testGceResourceAttributesMapping() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGcePlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -210,7 +210,7 @@ public void testGceResourceAttributesMapping() { } @Test - public void testGkeResourceAttributesMapping_LocationTypeRegion() { + void testGkeResourceAttributesMapping_LocationTypeRegion() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGkePlatform(GKE_LOCATION_TYPE_REGION); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -227,7 +227,7 @@ public void testGkeResourceAttributesMapping_LocationTypeRegion() { } @Test - public void testGkeResourceAttributesMapping_LocationTypeZone() { + void testGkeResourceAttributesMapping_LocationTypeZone() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGkePlatform(GKE_LOCATION_TYPE_ZONE); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -245,7 +245,7 @@ public void testGkeResourceAttributesMapping_LocationTypeZone() { } @Test - public void testGkeResourceAttributesMapping_LocationTypeInvalid() { + void testGkeResourceAttributesMapping_LocationTypeInvalid() { Map mockGKEAttributes = new HashMap<>(mockGKECommonAttributes); mockGKEAttributes.put(GKE_CLUSTER_LOCATION_TYPE, "INVALID"); mockGKEAttributes.put(GKE_CLUSTER_LOCATION, "some-location"); @@ -270,7 +270,7 @@ public void testGkeResourceAttributesMapping_LocationTypeInvalid() { } @Test - public void testGkeResourceAttributesMapping_LocationMissing() { + void testGkeResourceAttributesMapping_LocationMissing() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGkePlatform(""); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -295,7 +295,7 @@ private static void verifyGkeMapping(Resource gotResource, DetectedPlatform dete } @Test - public void testGcrServiceResourceAttributesMapping() { + void testGcrServiceResourceAttributesMapping() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockServerlessPlatform(GCPPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN); @@ -312,7 +312,7 @@ public void testGcrServiceResourceAttributesMapping() { } @Test - public void testGcfResourceAttributeMapping() { + void testGcfResourceAttributeMapping() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockServerlessPlatform( @@ -343,7 +343,7 @@ private static void verifyServerlessMapping( } @Test - public void testGcrJobResourceAttributesMapping() { + void testGcrJobResourceAttributesMapping() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGcrJobPlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -368,7 +368,7 @@ public void testGcrJobResourceAttributesMapping() { } @Test - public void testGaeResourceAttributeMapping() { + void testGaeResourceAttributeMapping() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGaePlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -390,7 +390,7 @@ public void testGaeResourceAttributeMapping() { } @Test - public void testUnknownPlatformResourceAttributesMapping() { + void testUnknownPlatformResourceAttributesMapping() { GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); DetectedPlatform mockPlatform = generateMockUnknownPlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -400,7 +400,7 @@ public void testUnknownPlatformResourceAttributesMapping() { } @Test - public void findsWithServiceLoader() { + void findsWithServiceLoader() { ServiceLoader services = ServiceLoader.load(ResourceProvider.class, getClass().getClassLoader()); while (services.iterator().hasNext()) { diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java index a0e06e336..b9964537f 100644 --- a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/InquireChannelCmdCollectorTest.java @@ -54,7 +54,7 @@ public void setup() throws Exception { } @Test - public void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { when(pcfMessageAgent.send(any(PCFMessage.class))) .thenReturn(createPCFResponseForInquireChannelCmd()); classUnderTest = new InquireChannelCmdCollector(meter); diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java index 6170e31f4..ea46450d3 100644 --- a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/ListenerMetricsCollectorTest.java @@ -47,7 +47,7 @@ public void setup() throws Exception { } @Test - public void testPublishMetrics() throws Exception { + void testPublishMetrics() throws Exception { when(pcfMessageAgent.send(any(PCFMessage.class))) .thenReturn(createPCFResponseForInquireListenerStatusCmd()); diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java index bd0c1ef97..5d18d73da 100644 --- a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueCollectionBuddyTest.java @@ -33,7 +33,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class QueueCollectionBuddyTest { +class QueueCollectionBuddyTest { @RegisterExtension static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java index dde400b02..3c6ef1503 100644 --- a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/QueueManagerMetricsCollectorTest.java @@ -51,7 +51,7 @@ public void setup() throws Exception { } @Test - public void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { + void testProcessPCFRequestAndPublishQMetricsForInquireQStatusCmd() throws Exception { when(pcfMessageAgent.send(any(PCFMessage.class))) .thenReturn(createPCFResponseForInquireQMgrStatusCmd()); classUnderTest = diff --git a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java index 865501573..646d4ee98 100644 --- a/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java +++ b/ibm-mq-metrics/src/test/java/io/opentelemetry/ibm/mq/metricscollector/TopicMetricsCollectorTest.java @@ -34,7 +34,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class TopicMetricsCollectorTest { +class TopicMetricsCollectorTest { @RegisterExtension static final OpenTelemetryExtension otelTesting = OpenTelemetryExtension.create(); diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java index 76a6333ba..b5ce0c650 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/InferredSpansAutoConfigTest.java @@ -35,7 +35,7 @@ import org.junit.jupiter.api.io.TempDir; @DisabledOnOs(WINDOWS) // Uses async-profiler, which is not supported on Windows -public class InferredSpansAutoConfigTest { +class InferredSpansAutoConfigTest { @BeforeEach @AfterEach @@ -99,7 +99,7 @@ public void checkAllOptions(@TempDir Path tmpDir) { } @Test - public void checkDisabledbyDefault() { + void checkDisabledbyDefault() { try (AutoConfigTestProperties props = new AutoConfigTestProperties()) { OpenTelemetry otel = GlobalOpenTelemetry.get(); List processors = OtelReflectionUtils.getSpanProcessors(otel); @@ -109,7 +109,7 @@ public void checkDisabledbyDefault() { @DisabledOnOpenJ9 @Test - public void checkProfilerWorking() { + void checkProfilerWorking() { try (AutoConfigTestProperties props = new AutoConfigTestProperties() .put(InferredSpansAutoConfig.ENABLED_OPTION, "true") diff --git a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerQueueTest.java b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerQueueTest.java index 614f75e72..bcf56691b 100644 --- a/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerQueueTest.java +++ b/inferred-spans/src/test/java/io/opentelemetry/contrib/inferredspans/internal/SamplingProfilerQueueTest.java @@ -17,7 +17,7 @@ import org.junit.jupiter.api.condition.DisabledOnOs; import org.junit.jupiter.api.condition.OS; -public class SamplingProfilerQueueTest { +class SamplingProfilerQueueTest { @Test @DisabledOnOs(OS.WINDOWS) diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java index d1431647b..c984724e3 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java @@ -28,7 +28,7 @@ * JmxConnectionBuilder and relies on containers to minimize the JMX/RMI network complications which * are not NAT-friendly. */ -public class JmxConnectionTest { +class JmxConnectionTest { // OTLP endpoint is not used in test mode, but still has to be provided private static final String DUMMY_OTLP_ENDPOINT = "http://dummy-otlp-endpoint:8080/"; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java index 4f909fdc5..19d98bf38 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/ActiveMqIntegrationTest.java @@ -15,7 +15,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; -public class ActiveMqIntegrationTest extends TargetSystemIntegrationTest { +class ActiveMqIntegrationTest extends TargetSystemIntegrationTest { private static final int ACTIVEMQ_PORT = 61616; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java index 90f1fca73..5ff8f2dcc 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CassandraIntegrationTest.java @@ -15,7 +15,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; -public class CassandraIntegrationTest extends TargetSystemIntegrationTest { +class CassandraIntegrationTest extends TargetSystemIntegrationTest { private static final int CASSANDRA_PORT = 9042; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CustomIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CustomIntegrationTest.java index dcecc8f5f..d238e8a72 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CustomIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/CustomIntegrationTest.java @@ -11,7 +11,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; -public class CustomIntegrationTest extends TargetSystemIntegrationTest { +class CustomIntegrationTest extends TargetSystemIntegrationTest { @Override protected GenericContainer createTargetContainer(int jmxPort) { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HBaseIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HBaseIntegrationTest.java index 803137539..4bab7e574 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HBaseIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HBaseIntegrationTest.java @@ -15,7 +15,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; -public class HBaseIntegrationTest extends TargetSystemIntegrationTest { +class HBaseIntegrationTest extends TargetSystemIntegrationTest { @Override protected GenericContainer createTargetContainer(int jmxPort) { return new GenericContainer<>("dajobe/hbase") diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HadoopIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HadoopIntegrationTest.java index b89225629..bc44ea36c 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HadoopIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/HadoopIntegrationTest.java @@ -16,7 +16,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.utility.MountableFile; -public class HadoopIntegrationTest extends TargetSystemIntegrationTest { +class HadoopIntegrationTest extends TargetSystemIntegrationTest { private static final int HADOOP_PORT = 50070; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java index 86097cbaa..8c91e0c85 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JettyIntegrationTest.java @@ -16,7 +16,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; -public class JettyIntegrationTest extends TargetSystemIntegrationTest { +class JettyIntegrationTest extends TargetSystemIntegrationTest { private static final int JETTY_PORT = 8080; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java index e29021a93..be8a04a53 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/JvmIntegrationTest.java @@ -15,7 +15,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.wait.strategy.Wait; -public class JvmIntegrationTest extends TargetSystemIntegrationTest { +class JvmIntegrationTest extends TargetSystemIntegrationTest { @Override protected GenericContainer createTargetContainer(int jmxPort) { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java index fbec81e2e..9cba55701 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java @@ -18,7 +18,7 @@ import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; -public class SolrIntegrationTest extends TargetSystemIntegrationTest { +class SolrIntegrationTest extends TargetSystemIntegrationTest { @Override protected GenericContainer createTargetContainer(int jmxPort) { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java index bec9453f8..b01f4485f 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TomcatIntegrationTest.java @@ -16,7 +16,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.images.builder.ImageFromDockerfile; -public class TomcatIntegrationTest extends TargetSystemIntegrationTest { +class TomcatIntegrationTest extends TargetSystemIntegrationTest { private static final int TOMCAT_PORT = 8080; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/WildflyIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/WildflyIntegrationTest.java index 73e546b85..bb2787b0a 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/WildflyIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/WildflyIntegrationTest.java @@ -22,7 +22,7 @@ import org.testcontainers.images.builder.ImageFromDockerfile; import org.testcontainers.utility.MountableFile; -public class WildflyIntegrationTest extends TargetSystemIntegrationTest { +class WildflyIntegrationTest extends TargetSystemIntegrationTest { private static final int WILDFLY_SERVICE_PORT = 8080; private static final int WILDFLY_MANAGEMENT_PORT = 9990; diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaConsumerIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaConsumerIntegrationTest.java index ed61aabbb..9319baf65 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaConsumerIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaConsumerIntegrationTest.java @@ -25,7 +25,7 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; -public class KafkaConsumerIntegrationTest extends TargetSystemIntegrationTest { +class KafkaConsumerIntegrationTest extends TargetSystemIntegrationTest { @Override protected Collection> createPrerequisiteContainers() { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaIntegrationTest.java index f59040509..17d38d995 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaIntegrationTest.java @@ -21,7 +21,7 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; -public class KafkaIntegrationTest extends TargetSystemIntegrationTest { +class KafkaIntegrationTest extends TargetSystemIntegrationTest { @Override protected Collection> createPrerequisiteContainers() { GenericContainer zookeeper = diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaProducerIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaProducerIntegrationTest.java index 155cb9fc5..37b141b01 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaProducerIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/kafka/KafkaProducerIntegrationTest.java @@ -24,7 +24,7 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; -public class KafkaProducerIntegrationTest extends TargetSystemIntegrationTest { +class KafkaProducerIntegrationTest extends TargetSystemIntegrationTest { @Override protected Collection> createPrerequisiteContainers() { diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java index e2cc37a28..0b94dce54 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/OpenTelemetrySdkServiceTest.java @@ -17,11 +17,11 @@ * Note: if otel-java-contrib bumps to Java 11+, we could use junit-pioneer's * {@code @SetSystemProperty} and {@code @ClearSystemProperty} but no bump is planned for now. */ -public class OpenTelemetrySdkServiceTest { +class OpenTelemetrySdkServiceTest { /** Verify default config */ @Test - public void testDefaultConfiguration() { + void testDefaultConfiguration() { System.clearProperty("otel.exporter.otlp.endpoint"); System.clearProperty("otel.service.name"); System.clearProperty("otel.resource.attributes"); @@ -40,7 +40,7 @@ public void testDefaultConfiguration() { /** Verify overwritten `service.name`,`key1` and `key2` */ @Test - public void testOverwrittenResourceAttributes() { + void testOverwrittenResourceAttributes() { System.setProperty("otel.service.name", "my-maven"); System.setProperty("otel.resource.attributes", "key1=val1,key2=val2"); @@ -59,7 +59,7 @@ public void testOverwrittenResourceAttributes() { /** Verify defining `otel.exporter.otlp.endpoint` works */ @Test - public void testOverwrittenExporterConfiguration_1() { + void testOverwrittenExporterConfiguration_1() { System.setProperty("otel.exporter.otlp.endpoint", "https://example.com:4317"); try (OpenTelemetrySdkService openTelemetrySdkService = new OpenTelemetrySdkService()) { @@ -78,7 +78,7 @@ public void testOverwrittenExporterConfiguration_1() { /** Verify defining `otel.exporter.otlp.traces.endpoint` works */ @Test - public void testOverwrittenExporterConfiguration_2() { + void testOverwrittenExporterConfiguration_2() { System.clearProperty("otel.exporter.otlp.endpoint"); System.clearProperty("otel.traces.exporter"); System.setProperty("otel.exporter.otlp.traces.endpoint", "https://example.com:4317/"); @@ -102,7 +102,7 @@ public void testOverwrittenExporterConfiguration_2() { /** Verify defining `otel.exporter.otlp.traces.endpoint` and `otel.traces.exporter` works */ @Test - public void testOverwrittenExporterConfiguration_3() { + void testOverwrittenExporterConfiguration_3() { System.clearProperty("otel.exporter.otlp.endpoint"); System.setProperty("otel.traces.exporter", "otlp"); System.setProperty("otel.exporter.otlp.traces.endpoint", "https://example.com:4317/"); diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/SpanRegistryTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/SpanRegistryTest.java index 864ce8df8..2a01a1754 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/SpanRegistryTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/SpanRegistryTest.java @@ -12,11 +12,11 @@ import io.opentelemetry.api.trace.Tracer; import org.junit.jupiter.api.Test; -public class SpanRegistryTest { +class SpanRegistryTest { /** MVND reuses the same Maven process and thus the Span Registry is reused. */ @Test - public void testSpanRegistryReuseWhenUsingMvnDaemon() { + void testSpanRegistryReuseWhenUsingMvnDaemon() { SpanRegistry spanRegistry = new SpanRegistry(); Tracer tracer = OpenTelemetry.noop().getTracer("test"); diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerConfigurationTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerConfigurationTest.java index 21748cebb..46877575a 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerConfigurationTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerConfigurationTest.java @@ -12,10 +12,9 @@ import java.util.Map; import org.junit.jupiter.api.Test; -public class MojoGoalExecutionHandlerConfigurationTest { - +class MojoGoalExecutionHandlerConfigurationTest { @Test - public void mojoGoalExecutionHandlers() { + void mojoGoalExecutionHandlers() { Map actual = MojoGoalExecutionHandlerConfiguration.loadMojoGoalExecutionHandler( OtelExecutionListener.class.getClassLoader()); diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerTest.java index b193ccdb5..645ecfa0d 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/handler/MojoGoalExecutionHandlerTest.java @@ -47,10 +47,10 @@ * https://github.com/takari/takari-lifecycle/blob/master/takari-lifecycle-plugin/src/test/java/io/takari/maven/plugins/plugin/PluginDescriptorMojoTest.java */ @SuppressWarnings({"DeduplicateConstants", "deprecation"}) -public class MojoGoalExecutionHandlerTest { +class MojoGoalExecutionHandlerTest { @Test - public void testMavenDeploy() throws Exception { + void testMavenDeploy() throws Exception { String pomXmlPath = "projects/jar/pom.xml"; String mojoGroupId = "org.apache.maven.plugins"; @@ -92,7 +92,7 @@ public void testMavenDeploy() throws Exception { } @Test - public void testSpringBootBuildImage_springboot_1() throws Exception { + void testSpringBootBuildImage_springboot_1() throws Exception { String pomXmlPath = "projects/springboot_1/pom.xml"; String mojoGroupId = "org.springframework.boot"; @@ -136,7 +136,7 @@ public void testSpringBootBuildImage_springboot_1() throws Exception { } @Test - public void testSpringBootBuildImage_springboot_2() throws Exception { + void testSpringBootBuildImage_springboot_2() throws Exception { String pomXmlPath = "projects/springboot_2/pom.xml"; String mojoGroupId = "org.springframework.boot"; @@ -180,7 +180,7 @@ public void testSpringBootBuildImage_springboot_2() throws Exception { } @Test - public void testGoogleJibBuild_jib_1() throws Exception { + void testGoogleJibBuild_jib_1() throws Exception { String pomXmlPath = "projects/jib_1/pom.xml"; String mojoGroupId = "com.google.cloud.tools"; @@ -221,7 +221,7 @@ public void testGoogleJibBuild_jib_1() throws Exception { } @Test - public void testGoogleJibBuild_jib_2() throws Exception { + void testGoogleJibBuild_jib_2() throws Exception { String pomXmlPath = "projects/jib_2/pom.xml"; String mojoGroupId = "com.google.cloud.tools"; @@ -262,7 +262,7 @@ public void testGoogleJibBuild_jib_2() throws Exception { } @Test - public void testSnykTest_snyk_1() throws Exception { + void testSnykTest_snyk_1() throws Exception { String pomXmlPath = "projects/snyk_1/pom.xml"; String mojoGroupId = "io.snyk"; @@ -298,7 +298,7 @@ public void testSnykTest_snyk_1() throws Exception { } @Test - public void testSnykMonitor_snyk_1() throws Exception { + void testSnykMonitor_snyk_1() throws Exception { String pomXmlPath = "projects/snyk_1/pom.xml"; String mojoGroupId = "io.snyk"; diff --git a/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java b/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java index 12be80e01..defefe634 100644 --- a/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java +++ b/micrometer-meter-provider/src/integrationTest/java/io/opentelemetry/contrib/metrics/micrometer/PrometheusIntegrationTest.java @@ -48,7 +48,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class PrometheusIntegrationTest { +class PrometheusIntegrationTest { private static final AttributeKey KEY1 = AttributeKey.stringKey("key1"); private static final AttributeKey KEY2 = AttributeKey.stringKey("key2"); private static final String VALUE1 = "value1"; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderTest.java index f323fbaba..4f1743cbc 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderTest.java @@ -18,7 +18,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerMeterProviderTest { +class MicrometerMeterProviderTest { SimpleMeterRegistry meterRegistry; CallbackRegistrar callbackRegistrar; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterTest.java index 56b173e29..c23ef99dc 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterTest.java @@ -31,7 +31,7 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -public class MicrometerMeterTest { +class MicrometerMeterTest { SimpleMeterRegistry meterRegistry; List callbacks; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java index fd7585605..5b755da5f 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleCounterTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerDoubleCounterTest { +class MicrometerDoubleCounterTest { SimpleMeterRegistry meterRegistry; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java index df358a1b9..393479db7 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleGaugeTest.java @@ -26,7 +26,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerDoubleGaugeTest { +class MicrometerDoubleGaugeTest { SimpleMeterRegistry meterRegistry; List callbacks; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java index bc88fccc7..dc62b91ee 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleHistogramTest.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerDoubleHistogramTest { +class MicrometerDoubleHistogramTest { SimpleMeterRegistry meterRegistry; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java index 57cf8922e..536f18d91 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerDoubleUpDownCounterTest.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerDoubleUpDownCounterTest { +class MicrometerDoubleUpDownCounterTest { SimpleMeterRegistry meterRegistry; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java index 803cc0952..34423fb14 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongCounterTest.java @@ -30,7 +30,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerLongCounterTest { +class MicrometerLongCounterTest { SimpleMeterRegistry meterRegistry; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java index f55087dcf..96b56b1a9 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongGaugeTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerLongGaugeTest { +class MicrometerLongGaugeTest { SimpleMeterRegistry meterRegistry; List callbacks; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java index 880e7d766..a2651d4b6 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongHistogramTest.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerLongHistogramTest { +class MicrometerLongHistogramTest { SimpleMeterRegistry meterRegistry; diff --git a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java index e4bce0d99..bc73dcb5d 100644 --- a/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java +++ b/micrometer-meter-provider/src/test/java/io/opentelemetry/contrib/metrics/micrometer/internal/instruments/MicrometerLongUpDownCounterTest.java @@ -29,7 +29,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class MicrometerLongUpDownCounterTest { +class MicrometerLongUpDownCounterTest { SimpleMeterRegistry meterRegistry; diff --git a/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java b/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java index 3b775a527..05bee8aa8 100644 --- a/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java +++ b/processors/src/test/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessorTest.java @@ -31,7 +31,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -public class FilteringLogRecordProcessorTest { +class FilteringLogRecordProcessorTest { private final InMemoryLogRecordExporter memoryLogRecordExporter = InMemoryLogRecordExporter.create(); diff --git a/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java b/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java index 5a63d28e7..75d5664a2 100644 --- a/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java +++ b/resource-providers/src/test/java/io/opentelemetry/contrib/resourceproviders/JettyServiceNameDetectorTest.java @@ -14,7 +14,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.io.TempDir; -public class JettyServiceNameDetectorTest { +class JettyServiceNameDetectorTest { @Test void testJettyBase(@TempDir Path tempDir) throws IOException { diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java index 43d89b2ef..611dde652 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AbstractAttachmentTest.java @@ -8,7 +8,7 @@ import io.opentelemetry.javaagent.shaded.io.opentelemetry.api.trace.Span; import org.junit.jupiter.api.BeforeAll; -public class AbstractAttachmentTest { +class AbstractAttachmentTest { @BeforeAll static void disableMainThreadCheck() { diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java index 41571a2e4..91a67d3cd 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledByEnvironmentVariableTest.java @@ -10,7 +10,7 @@ import io.opentelemetry.instrumentation.annotations.WithSpan; import org.junit.jupiter.api.Test; -public class AgentDisabledByEnvironmentVariableTest extends AbstractAttachmentTest { +class AgentDisabledByEnvironmentVariableTest extends AbstractAttachmentTest { @Test void shouldNotAttachWhenAgentDisabledWithEnvVariable() { diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java index a24c81296..39d941ff3 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/AgentDisabledBySystemPropertyTest.java @@ -10,7 +10,7 @@ import io.opentelemetry.instrumentation.annotations.WithSpan; import org.junit.jupiter.api.Test; -public class AgentDisabledBySystemPropertyTest extends AbstractAttachmentTest { +class AgentDisabledBySystemPropertyTest extends AbstractAttachmentTest { @Test void shouldNotAttachWhenAgentDisabledWithProperty() { diff --git a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java index 5d8f201ae..65eabcf02 100644 --- a/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java +++ b/runtime-attach/runtime-attach/src/test/java/io/opentelemetry/contrib/attach/RunTimeAttachBasicTest.java @@ -10,7 +10,7 @@ import io.opentelemetry.instrumentation.annotations.WithSpan; import org.junit.jupiter.api.Test; -public class RunTimeAttachBasicTest extends AbstractAttachmentTest { +class RunTimeAttachBasicTest extends AbstractAttachmentTest { @Test void shouldAttach() { diff --git a/samplers/src/test/java/io/opentelemetry/contrib/sampler/RuleBasedRoutingSamplerTest.java b/samplers/src/test/java/io/opentelemetry/contrib/sampler/RuleBasedRoutingSamplerTest.java index 05a4301e6..62b1bee31 100644 --- a/samplers/src/test/java/io/opentelemetry/contrib/sampler/RuleBasedRoutingSamplerTest.java +++ b/samplers/src/test/java/io/opentelemetry/contrib/sampler/RuleBasedRoutingSamplerTest.java @@ -63,7 +63,7 @@ public void setup() { } @Test - public void testThatThrowsOnNullParameter() { + void testThatThrowsOnNullParameter() { assertThatExceptionOfType(NullPointerException.class) .isThrownBy(() -> new RuleBasedRoutingSampler(patterns, SPAN_KIND, null)); @@ -98,7 +98,7 @@ public void testThatThrowsOnNullParameter() { } @Test - public void testThatDelegatesIfNoRulesGiven() { + void testThatDelegatesIfNoRulesGiven() { RuleBasedRoutingSampler sampler = RuleBasedRoutingSampler.builder(SPAN_KIND, delegate).build(); // no http.url attribute @@ -117,7 +117,7 @@ public void testThatDelegatesIfNoRulesGiven() { } @Test - public void testDropOnExactMatch() { + void testDropOnExactMatch() { RuleBasedRoutingSampler sampler = addRules(RuleBasedRoutingSampler.builder(SPAN_KIND, delegate)).build(); assertThat(shouldSample(sampler, "https://example.com/healthcheck").getDecision()) @@ -125,7 +125,7 @@ public void testDropOnExactMatch() { } @Test - public void testDelegateOnDifferentKind() { + void testDelegateOnDifferentKind() { RuleBasedRoutingSampler sampler = addRules(RuleBasedRoutingSampler.builder(SpanKind.CLIENT, delegate)).build(); assertThat(shouldSample(sampler, "https://example.com/healthcheck").getDecision()) @@ -134,7 +134,7 @@ public void testDelegateOnDifferentKind() { } @Test - public void testDelegateOnNoMatch() { + void testDelegateOnNoMatch() { RuleBasedRoutingSampler sampler = addRules(RuleBasedRoutingSampler.builder(SPAN_KIND, delegate)).build(); assertThat(shouldSample(sampler, "https://example.com/customers").getDecision()) @@ -143,7 +143,7 @@ public void testDelegateOnNoMatch() { } @Test - public void testDelegateOnMalformedUrl() { + void testDelegateOnMalformedUrl() { RuleBasedRoutingSampler sampler = addRules(RuleBasedRoutingSampler.builder(SPAN_KIND, delegate)).build(); assertThat(shouldSample(sampler, "abracadabra").getDecision()) @@ -158,7 +158,7 @@ public void testDelegateOnMalformedUrl() { } @Test - public void testVerifiesAllGivenAttributes() { + void testVerifiesAllGivenAttributes() { RuleBasedRoutingSampler sampler = addRules(RuleBasedRoutingSampler.builder(SPAN_KIND, delegate)).build(); Attributes attributes = Attributes.of(URL_PATH, "/actuator/info"); diff --git a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfigTest.java b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfigTest.java index 4b8f999f4..ca1517480 100644 --- a/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfigTest.java +++ b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceAutoConfigTest.java @@ -15,7 +15,7 @@ import java.util.function.Predicate; import org.junit.jupiter.api.Test; -public class StackTraceAutoConfigTest { +class StackTraceAutoConfigTest { @Test void defaultConfig() { From 00787f59d2e9402db0439473303f25f650b370d1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 10:42:28 -0700 Subject: [PATCH 269/371] Update style guide (#2210) --- docs/style-guide.md | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/docs/style-guide.md b/docs/style-guide.md index 54dc63cb5..9acf641ae 100644 --- a/docs/style-guide.md +++ b/docs/style-guide.md @@ -78,10 +78,9 @@ packages contain implementation details that should not be used by external cons ### `final` keyword usage -Public non-internal classes should be declared `final` where possible. Internal and non-public -classes should not be declared `final`. +Public non-internal non-test classes should be declared `final` where possible. -Methods should only be declared `final` if they are in public non-internal non-final classes. +Methods should only be declared `final` if they are in public non-internal non-test non-final classes. Fields should be declared `final` where possible. @@ -124,19 +123,6 @@ Test classes and test methods should generally be package-protected (no explicit modifier) rather than `public`. This follows the principle of minimal necessary visibility and is sufficient for JUnit to discover and execute tests. -### AutoService - -Use the `@AutoService` annotation when implementing SPI interfaces. This automatically generates the -necessary `META-INF/services/` files at compile time, eliminating the need to manually create and -maintain service registration files. - -```java -@AutoService(AutoConfigurationCustomizerProvider.class) -public class MyCustomizerProvider implements AutoConfigurationCustomizerProvider { - // implementation -} -``` - ### Gradle - Use Kotlin instead of Groovy for build scripts From 7d647751a1ea4723dda6c2e943dbc851a113fbcf Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 10:42:39 -0700 Subject: [PATCH 270/371] Apply final to public API classes where possible - disk-buffering (#2216) --- .../contrib/disk/buffering/LogRecordFromDiskExporter.java | 2 +- .../contrib/disk/buffering/LogRecordToDiskExporter.java | 3 ++- .../contrib/disk/buffering/MetricFromDiskExporter.java | 2 +- .../contrib/disk/buffering/MetricToDiskExporter.java | 2 +- .../contrib/disk/buffering/SpanFromDiskExporter.java | 2 +- .../contrib/disk/buffering/SpanToDiskExporter.java | 2 +- 6 files changed, 7 insertions(+), 6 deletions(-) diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java index c26a383d6..6c38be04a 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; -public class LogRecordFromDiskExporter implements FromDiskExporter { +public final class LogRecordFromDiskExporter implements FromDiskExporter { private final FromDiskExporterImpl delegate; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java index 665e90f76..1524723e8 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java @@ -18,7 +18,8 @@ * This class implements a {@link LogRecordExporter} that delegates to an instance of {@code * ToDiskExporter}. */ -public class LogRecordToDiskExporter implements LogRecordExporter { +public final class LogRecordToDiskExporter implements LogRecordExporter { + private final ToDiskExporter delegate; /** diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java index 8bb4f3dcd..36d478e7b 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; -public class MetricFromDiskExporter implements FromDiskExporter { +public final class MetricFromDiskExporter implements FromDiskExporter { private final FromDiskExporterImpl delegate; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java index 83d2fc73c..2e0848684 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java @@ -21,7 +21,7 @@ * This class implements a {@link MetricExporter} that delegates to an instance of {@code * ToDiskExporter}. */ -public class MetricToDiskExporter implements MetricExporter { +public final class MetricToDiskExporter implements MetricExporter { private final ToDiskExporter delegate; private final AggregationTemporalitySelector aggregationTemporalitySelector; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java index e3c7992ba..9523c8a2f 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java @@ -14,7 +14,7 @@ import java.io.IOException; import java.util.concurrent.TimeUnit; -public class SpanFromDiskExporter implements FromDiskExporter { +public final class SpanFromDiskExporter implements FromDiskExporter { private final FromDiskExporterImpl delegate; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java index d5ca81518..dcd79d3b0 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java @@ -18,7 +18,7 @@ * This class implements a SpanExporter that delegates to an instance of {@code * ToDiskExporter}. */ -public class SpanToDiskExporter implements SpanExporter { +public final class SpanToDiskExporter implements SpanExporter { private final ToDiskExporter delegate; From 0bb958595dec97dd56ca06e218f191227236f04a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 10:42:46 -0700 Subject: [PATCH 271/371] Apply final to public API classes where possible - jmx-scraper, jmx-metrics (#2215) --- .../contrib/jmxscraper/InvalidArgumentException.java | 2 +- .../opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java | 2 +- .../java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java | 2 +- .../contrib/jmxscraper/config/JmxScraperConfig.java | 2 +- .../contrib/jmxscraper/config/PropertiesCustomizer.java | 2 +- .../contrib/jmxscraper/config/PropertiesSupplier.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/InvalidArgumentException.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/InvalidArgumentException.java index bdfb93272..d3358741f 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/InvalidArgumentException.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/InvalidArgumentException.java @@ -9,7 +9,7 @@ * Exception indicating something is wrong with the provided arguments or reading the configuration * from them */ -public class InvalidArgumentException extends Exception { +public final class InvalidArgumentException extends Exception { private static final long serialVersionUID = 0L; diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java index b3c432db6..92ff4f387 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxConnectorBuilder.java @@ -34,7 +34,7 @@ import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.RealmCallback; -public class JmxConnectorBuilder { +public final class JmxConnectorBuilder { private static final Logger logger = Logger.getLogger(JmxConnectorBuilder.class.getName()); diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index a5e0d4de1..0835d9d92 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -35,7 +35,7 @@ import javax.management.MBeanServerConnection; import javax.management.remote.JMXConnector; -public class JmxScraper { +public final class JmxScraper { private static final Logger logger = Logger.getLogger(JmxScraper.class.getName()); private static final String CONFIG_ARG = "-config"; private static final String TEST_ARG = "-test"; diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java index 62b6c38ea..53c19096b 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/JmxScraperConfig.java @@ -23,7 +23,7 @@ import javax.annotation.Nullable; /** This class keeps application settings */ -public class JmxScraperConfig { +public final class JmxScraperConfig { private static final Logger logger = Logger.getLogger(JmxScraperConfig.class.getName()); diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java index 9d6812146..141ad025f 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java @@ -16,7 +16,7 @@ import javax.annotation.Nullable; /** Customizer of default SDK configuration and provider of effective scraper config */ -public class PropertiesCustomizer implements Function> { +public final class PropertiesCustomizer implements Function> { private static final Logger logger = Logger.getLogger(PropertiesCustomizer.class.getName()); diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesSupplier.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesSupplier.java index 071e2b8fa..f4ad68faa 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesSupplier.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesSupplier.java @@ -11,7 +11,7 @@ import java.util.function.Supplier; /** Configuration supplier for java properties */ -public class PropertiesSupplier implements Supplier> { +public final class PropertiesSupplier implements Supplier> { private final Properties properties; From 0bf5518c0e088ad18ddcf2e3153dcd18a4f47ad3 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 10:58:55 -0700 Subject: [PATCH 272/371] Apply final to public API classes where possible - azure-resources, cloudfoundry-resources, baggage-processor (#2213) --- .../contrib/azure/resource/AzureAksResourceProvider.java | 2 +- .../contrib/azure/resource/AzureAppServiceResourceProvider.java | 2 +- .../contrib/azure/resource/AzureContainersResourceProvider.java | 2 +- .../contrib/azure/resource/AzureFunctionsResourceProvider.java | 2 +- .../contrib/azure/resource/AzureMetadataService.java | 2 +- .../contrib/azure/resource/AzureResourceDetector.java | 2 +- .../contrib/azure/resource/AzureVmResourceProvider.java | 2 +- .../contrib/baggage/processor/BaggageLogRecordProcessor.java | 2 +- .../contrib/baggage/processor/BaggageProcessorCustomizer.java | 2 +- .../contrib/baggage/processor/BaggageSpanProcessor.java | 2 +- .../cloudfoundry/resources/CloudFoundryResourceDetector.java | 2 +- .../cloudfoundry/resources/CloudFoundryResourceProvider.java | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java index 9823c6f9e..987492dd8 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAksResourceProvider.java @@ -14,7 +14,7 @@ import java.util.Optional; import java.util.function.Supplier; -public class AzureAksResourceProvider extends CloudResourceProvider { +public final class AzureAksResourceProvider extends CloudResourceProvider { private static final Map COMPUTE_MAPPING = new HashMap<>(); diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java index 485ef7b01..3a658428a 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProvider.java @@ -23,7 +23,7 @@ import java.util.Map; import javax.annotation.Nullable; -public class AzureAppServiceResourceProvider extends CloudResourceProvider { +public final class AzureAppServiceResourceProvider extends CloudResourceProvider { static final AttributeKey AZURE_APP_SERVICE_STAMP_RESOURCE_ATTRIBUTE = AttributeKey.stringKey("azure.app.service.stamp"); diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java index 63d0f4428..014ec5b41 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureContainersResourceProvider.java @@ -16,7 +16,7 @@ import java.util.HashMap; import java.util.Map; -public class AzureContainersResourceProvider extends CloudResourceProvider { +public final class AzureContainersResourceProvider extends CloudResourceProvider { static final String CONTAINER_APP_NAME = "CONTAINER_APP_NAME"; diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java index e35fe95c4..d98a41be4 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProvider.java @@ -19,7 +19,7 @@ import java.util.HashMap; import java.util.Map; -public class AzureFunctionsResourceProvider extends CloudResourceProvider { +public final class AzureFunctionsResourceProvider extends CloudResourceProvider { static final String FUNCTIONS_VERSION = "FUNCTIONS_EXTENSION_VERSION"; private static final String FUNCTIONS_MEM_LIMIT = "WEBSITE_MEMORY_LIMIT_MB"; diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java index ced58f0d2..a93413a24 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureMetadataService.java @@ -20,7 +20,7 @@ import okhttp3.Request; import okhttp3.Response; -public class AzureMetadataService { +public final class AzureMetadataService { static final JsonFactory JSON_FACTORY = new JsonFactory(); private static final URL METADATA_URL; private static final Duration TIMEOUT = Duration.ofSeconds(5); diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java index 556e1d60c..27da91c4c 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureResourceDetector.java @@ -11,7 +11,7 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.resources.ResourceBuilder; -public class AzureResourceDetector implements ComponentProvider { +public final class AzureResourceDetector implements ComponentProvider { @Override public Class getType() { diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java index f36590622..2a87a0488 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/AzureVmResourceProvider.java @@ -34,7 +34,7 @@ import java.util.logging.Logger; import org.jetbrains.annotations.NotNull; -public class AzureVmResourceProvider extends CloudResourceProvider { +public final class AzureVmResourceProvider extends CloudResourceProvider { private static final Map COMPUTE_MAPPING = new HashMap<>(); diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java index b3701bf52..474f4caef 100644 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageLogRecordProcessor.java @@ -16,7 +16,7 @@ * This log record processor copies attributes stored in {@link Baggage} into each newly created log * record. */ -public class BaggageLogRecordProcessor implements LogRecordProcessor { +public final class BaggageLogRecordProcessor implements LogRecordProcessor { private final Predicate baggageKeyPredicate; diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java index 11f85e609..2e07722e6 100644 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageProcessorCustomizer.java @@ -14,7 +14,7 @@ import java.util.List; @AutoService(AutoConfigurationCustomizerProvider.class) -public class BaggageProcessorCustomizer implements AutoConfigurationCustomizerProvider { +public final class BaggageProcessorCustomizer implements AutoConfigurationCustomizerProvider { @Override public void customize(AutoConfigurationCustomizer autoConfigurationCustomizer) { autoConfigurationCustomizer diff --git a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java index 5f0f53d03..1ba62b19d 100644 --- a/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java +++ b/baggage-processor/src/main/java/io/opentelemetry/contrib/baggage/processor/BaggageSpanProcessor.java @@ -16,7 +16,7 @@ * This span processor copies attributes stored in {@link Baggage} into each newly created {@link * io.opentelemetry.api.trace.Span}. */ -public class BaggageSpanProcessor implements SpanProcessor { +public final class BaggageSpanProcessor implements SpanProcessor { private final Predicate baggageKeyPredicate; /** diff --git a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java index 02b9a6b09..357d83533 100644 --- a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java +++ b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceDetector.java @@ -9,7 +9,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; import io.opentelemetry.sdk.resources.Resource; -public class CloudFoundryResourceDetector implements ComponentProvider { +public final class CloudFoundryResourceDetector implements ComponentProvider { @Override public Class getType() { diff --git a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceProvider.java b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceProvider.java index e3f3e3c64..992eb93dc 100644 --- a/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceProvider.java +++ b/cloudfoundry-resources/src/main/java/io/opentelemetry/contrib/cloudfoundry/resources/CloudFoundryResourceProvider.java @@ -9,7 +9,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; import io.opentelemetry.sdk.resources.Resource; -public class CloudFoundryResourceProvider implements ResourceProvider { +public final class CloudFoundryResourceProvider implements ResourceProvider { @Override public Resource createResource(ConfigProperties configProperties) { From fb1ef873cd75b0356cab1411128942ad330b2019 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 13:17:12 -0700 Subject: [PATCH 273/371] Apply final to public API classes where possible - kafka-exporter, micrometer-meter-provider, noop-api, processors, resource-providers, samplers (#2217) --- .../io/opentelemetry/contrib/kafka/KafkaSpanExporter.java | 2 +- .../contrib/kafka/KafkaSpanExporterBuilder.java | 2 +- .../opentelemetry/contrib/kafka/SpanDataDeserializer.java | 2 +- .../io/opentelemetry/contrib/kafka/SpanDataSerializer.java | 2 +- .../metrics/micrometer/MicrometerMeterProviderBuilder.java | 2 +- .../contrib/noopapi/NoopContextStorageProvider.java | 2 +- .../io/opentelemetry/contrib/noopapi/NoopOpenTelemetry.java | 2 +- .../contrib/filter/FilteringLogRecordProcessor.java | 6 +++--- .../resourceproviders/AppServerResourceDetector.java | 2 +- .../contrib/sampler/LinksParentAlwaysOnSamplerProvider.java | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java index 4b79b3a13..28e123fe8 100644 --- a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java +++ b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporter.java @@ -26,7 +26,7 @@ @ThreadSafe @SuppressWarnings("FutureReturnValueIgnored") -public class KafkaSpanExporter implements SpanExporter { +public final class KafkaSpanExporter implements SpanExporter { private static final Logger logger = LoggerFactory.getLogger(KafkaSpanExporter.class); private final String topicName; private final Producer> producer; diff --git a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilder.java b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilder.java index e6b49d6dc..d7f1d3c17 100644 --- a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilder.java +++ b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/KafkaSpanExporterBuilder.java @@ -20,7 +20,7 @@ import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.common.serialization.Serializer; -public class KafkaSpanExporterBuilder { +public final class KafkaSpanExporterBuilder { private static final long DEFAULT_TIMEOUT_IN_SECONDS = 5L; private String topicName; private Producer> producer; diff --git a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataDeserializer.java b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataDeserializer.java index 4c5ff7112..9da3f266f 100644 --- a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataDeserializer.java +++ b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataDeserializer.java @@ -11,7 +11,7 @@ import org.apache.kafka.common.errors.SerializationException; import org.apache.kafka.common.serialization.Deserializer; -public class SpanDataDeserializer implements Deserializer { +public final class SpanDataDeserializer implements Deserializer { @SuppressWarnings("NullAway") @Override public ExportTraceServiceRequest deserialize(String topic, byte[] data) { diff --git a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataSerializer.java b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataSerializer.java index 4c689f16a..8f31eb412 100644 --- a/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataSerializer.java +++ b/kafka-exporter/src/main/java/io/opentelemetry/contrib/kafka/SpanDataSerializer.java @@ -20,7 +20,7 @@ import org.apache.kafka.common.errors.SerializationException; import org.apache.kafka.common.serialization.Serializer; -public class SpanDataSerializer implements Serializer> { +public final class SpanDataSerializer implements Serializer> { @Override public byte[] serialize(String topic, Collection data) { if (Objects.isNull(data)) { diff --git a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderBuilder.java b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderBuilder.java index 655c8cd32..25bd8b4d3 100644 --- a/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderBuilder.java +++ b/micrometer-meter-provider/src/main/java/io/opentelemetry/contrib/metrics/micrometer/MicrometerMeterProviderBuilder.java @@ -12,7 +12,7 @@ import javax.annotation.Nullable; /** Builder utility class for creating instances of {@link MicrometerMeterProvider}. */ -public class MicrometerMeterProviderBuilder { +public final class MicrometerMeterProviderBuilder { private final Supplier meterRegistrySupplier; @Nullable private CallbackRegistrar callbackRegistrar; diff --git a/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopContextStorageProvider.java b/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopContextStorageProvider.java index 32a33dde2..358730308 100644 --- a/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopContextStorageProvider.java +++ b/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopContextStorageProvider.java @@ -15,7 +15,7 @@ /** * A {@link ContextStorageProvider} that returns a {@link ContextStorage} which is completely no-op. */ -public class NoopContextStorageProvider implements ContextStorageProvider { +public final class NoopContextStorageProvider implements ContextStorageProvider { /** Returns a no-op context storage. */ @Override diff --git a/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopOpenTelemetry.java b/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopOpenTelemetry.java index b84fef8b1..41da9ca7e 100644 --- a/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopOpenTelemetry.java +++ b/noop-api/src/main/java/io/opentelemetry/contrib/noopapi/NoopOpenTelemetry.java @@ -42,7 +42,7 @@ *

The above will succeed both with the {@linkplain OpenTelemetry#noop() default implementation} * and this one, but with this implementation there will be no overhead at all. */ -public class NoopOpenTelemetry implements OpenTelemetry { +public final class NoopOpenTelemetry implements OpenTelemetry { private static final OpenTelemetry INSTANCE = new NoopOpenTelemetry(); diff --git a/processors/src/main/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessor.java b/processors/src/main/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessor.java index a213ec21a..61347388a 100644 --- a/processors/src/main/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessor.java +++ b/processors/src/main/java/io/opentelemetry/contrib/filter/FilteringLogRecordProcessor.java @@ -11,10 +11,10 @@ import io.opentelemetry.sdk.logs.data.LogRecordData; import java.util.function.Predicate; -public class FilteringLogRecordProcessor implements LogRecordProcessor { +public final class FilteringLogRecordProcessor implements LogRecordProcessor { - public final LogRecordProcessor delegate; - public final Predicate predicate; + private final LogRecordProcessor delegate; + private final Predicate predicate; public FilteringLogRecordProcessor( LogRecordProcessor delegate, Predicate predicate) { diff --git a/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java b/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java index 7d5d399c4..d97785cca 100644 --- a/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java +++ b/resource-providers/src/main/java/io/opentelemetry/contrib/resourceproviders/AppServerResourceDetector.java @@ -12,7 +12,7 @@ @SuppressWarnings("rawtypes") @AutoService(ComponentProvider.class) -public class AppServerResourceDetector implements ComponentProvider { +public final class AppServerResourceDetector implements ComponentProvider { @Override public Class getType() { diff --git a/samplers/src/main/java/io/opentelemetry/contrib/sampler/LinksParentAlwaysOnSamplerProvider.java b/samplers/src/main/java/io/opentelemetry/contrib/sampler/LinksParentAlwaysOnSamplerProvider.java index 45f341897..8024ab590 100644 --- a/samplers/src/main/java/io/opentelemetry/contrib/sampler/LinksParentAlwaysOnSamplerProvider.java +++ b/samplers/src/main/java/io/opentelemetry/contrib/sampler/LinksParentAlwaysOnSamplerProvider.java @@ -9,7 +9,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider; import io.opentelemetry.sdk.trace.samplers.Sampler; -public class LinksParentAlwaysOnSamplerProvider implements ConfigurableSamplerProvider { +public final class LinksParentAlwaysOnSamplerProvider implements ConfigurableSamplerProvider { @Override public Sampler createSampler(ConfigProperties config) { return LinksBasedSampler.create(Sampler.parentBased(Sampler.alwaysOn())); From d5e384069a4b2ee0f00ae0197b8977388a7498ab Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 11 Sep 2025 09:30:48 +0300 Subject: [PATCH 274/371] chore(deps): update github/codeql-action action to v3.30.3 (#2248) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4fb19276c..d22ef1ab5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Initialize CodeQL - uses: github/codeql-action/init@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 + uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 + uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index fa03c4b84..e939d15ee 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@d3678e237b9c32a6c9bffb3315c335f976f3549f # v3.30.2 + uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 with: sarif_file: results.sarif From 9bde959f859e766208f72c1f961d4de823cac82d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 10 Sep 2025 23:32:20 -0700 Subject: [PATCH 275/371] Ignore broken IBM links for now (#2247) --- .github/config/lychee.toml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/config/lychee.toml b/.github/config/lychee.toml index b2d5285f1..bde272fc9 100644 --- a/.github/config/lychee.toml +++ b/.github/config/lychee.toml @@ -7,6 +7,8 @@ max_concurrency = 4 include_fragments = true exclude = [ + # until https://github.com/open-telemetry/opentelemetry-java-contrib/issues/2221 is resolved + "^https?://www.ibm.com", # excluding links to pull requests and issues is done for performance "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$", ] From a7f075f01fe130b9e1594fc9bf0c1ca696dbb34b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 12 Sep 2025 07:10:16 -0700 Subject: [PATCH 276/371] fix(deps): update dependency com.google.protobuf:protobuf-bom to v4.32.1 (#2251) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 4afbf4a6a..637f8fbac 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -16,7 +16,7 @@ dependencies { // as runtime dependencies if they are actually used as runtime dependencies) api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.20.0")) - api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.32.0")) + api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.32.1")) api(enforcedPlatform("com.squareup.okhttp3:okhttp-bom:5.1.0")) constraints { From 095a4dbfdbade10bb538a20b6545e75f6488b193 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Fri, 12 Sep 2025 07:40:57 -0700 Subject: [PATCH 277/371] [OpAmp] Move important user-facing classes out of 'internal' package. (#2249) --- .../opamp/client/{internal => }/OpampClient.java | 2 +- .../client/{internal => }/OpampClientBuilder.java | 11 +++-------- .../opamp/client/internal/impl/OpampClientImpl.java | 4 ++-- .../internal/request/service/HttpRequestService.java | 1 + .../request/service/WebSocketRequestService.java | 1 + .../opamp/client/internal/response/MessageData.java | 2 +- .../request/service/RequestService.java | 6 ++++-- .../client/internal/impl/OpampClientImplTest.java | 4 ++-- .../request/service/HttpRequestServiceTest.java | 1 + .../request/service/WebSocketRequestServiceTest.java | 1 + 10 files changed, 17 insertions(+), 16 deletions(-) rename opamp-client/src/main/java/io/opentelemetry/opamp/client/{internal => }/OpampClient.java (98%) rename opamp-client/src/main/java/io/opentelemetry/opamp/client/{internal => }/OpampClientBuilder.java (98%) rename opamp-client/src/main/java/io/opentelemetry/opamp/client/{internal => }/request/service/RequestService.java (91%) diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java similarity index 98% rename from opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java rename to opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java index 5889642d8..19d63eb11 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClient.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.opamp.client.internal; +package io.opentelemetry.opamp.client; import io.opentelemetry.opamp.client.internal.response.MessageData; import java.io.Closeable; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClientBuilder.java similarity index 98% rename from opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java rename to opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClientBuilder.java index 3e4d0f4aa..d6af850fa 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/OpampClientBuilder.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClientBuilder.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.opamp.client.internal; +package io.opentelemetry.opamp.client; import com.github.f4b6a3.uuid.UuidCreator; import com.google.errorprone.annotations.CanIgnoreReturnValue; @@ -11,9 +11,9 @@ import io.opentelemetry.opamp.client.internal.impl.OpampClientImpl; import io.opentelemetry.opamp.client.internal.impl.OpampClientState; import io.opentelemetry.opamp.client.internal.request.service.HttpRequestService; -import io.opentelemetry.opamp.client.internal.request.service.RequestService; import io.opentelemetry.opamp.client.internal.request.service.WebSocketRequestService; import io.opentelemetry.opamp.client.internal.state.State; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.HashMap; @@ -28,12 +28,7 @@ import opamp.proto.KeyValue; import opamp.proto.RemoteConfigStatus; -/** - * This class is internal and is hence not for public use. Its APIs are unstable and can change at - * any time. - * - *

Builds an {@link OpampClient} instance. - */ +/** Builds an {@link OpampClient} instance. */ public final class OpampClientBuilder { private final Map identifyingAttributes = new HashMap<>(); private final Map nonIdentifyingAttributes = new HashMap<>(); diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java index 7ada06c42..e224135ff 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java @@ -5,7 +5,7 @@ package io.opentelemetry.opamp.client.internal.impl; -import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.OpampClient; import io.opentelemetry.opamp.client.internal.impl.recipe.AgentToServerAppenders; import io.opentelemetry.opamp.client.internal.impl.recipe.RecipeManager; import io.opentelemetry.opamp.client.internal.impl.recipe.RequestRecipe; @@ -19,12 +19,12 @@ import io.opentelemetry.opamp.client.internal.impl.recipe.appenders.SequenceNumberAppender; import io.opentelemetry.opamp.client.internal.request.Field; import io.opentelemetry.opamp.client.internal.request.Request; -import io.opentelemetry.opamp.client.internal.request.service.RequestService; import io.opentelemetry.opamp.client.internal.response.MessageData; import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; import io.opentelemetry.opamp.client.internal.state.ObservableState; import io.opentelemetry.opamp.client.internal.state.State; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.util.ArrayList; import java.util.Collections; import java.util.List; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java index 569447342..e9f79c254 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java @@ -14,6 +14,7 @@ import io.opentelemetry.opamp.client.internal.request.delay.RetryPeriodicDelay; import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.io.IOException; import java.time.Duration; import java.util.Objects; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java index 13ef9b117..280725884 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestService.java @@ -13,6 +13,7 @@ import io.opentelemetry.opamp.client.internal.request.delay.RetryPeriodicDelay; import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Duration; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java index df9fb8ba1..af47027ef 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/response/MessageData.java @@ -6,7 +6,7 @@ package io.opentelemetry.opamp.client.internal.response; import com.google.auto.value.AutoValue; -import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.OpampClient; import javax.annotation.Nullable; import opamp.proto.AgentRemoteConfig; diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/request/service/RequestService.java similarity index 91% rename from opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java rename to opamp-client/src/main/java/io/opentelemetry/opamp/client/request/service/RequestService.java index ee47e4249..76d1650e3 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/RequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/request/service/RequestService.java @@ -3,10 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.opamp.client.internal.request.service; +package io.opentelemetry.opamp.client.request.service; -import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.OpampClient; import io.opentelemetry.opamp.client.internal.request.Request; +import io.opentelemetry.opamp.client.internal.request.service.HttpRequestService; +import io.opentelemetry.opamp.client.internal.request.service.WebSocketRequestService; import io.opentelemetry.opamp.client.internal.response.Response; import java.util.function.Supplier; diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java index 9a076f757..dd5c0b956 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java @@ -12,13 +12,13 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; -import io.opentelemetry.opamp.client.internal.OpampClient; +import io.opentelemetry.opamp.client.OpampClient; import io.opentelemetry.opamp.client.internal.connectivity.http.OkHttpSender; import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.request.service.HttpRequestService; -import io.opentelemetry.opamp.client.internal.request.service.RequestService; import io.opentelemetry.opamp.client.internal.response.MessageData; import io.opentelemetry.opamp.client.internal.state.State; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.io.IOException; import java.time.Duration; import java.util.ArrayList; diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java index c0107673b..fad784e62 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java @@ -21,6 +21,7 @@ import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.request.delay.PeriodicDelay; import io.opentelemetry.opamp.client.internal.response.Response; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.io.ByteArrayInputStream; import java.time.Duration; import java.util.ArrayList; diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java index c00cc0cc0..becdbc69b 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java @@ -23,6 +23,7 @@ import io.opentelemetry.opamp.client.internal.request.Request; import io.opentelemetry.opamp.client.internal.response.OpampServerResponseException; import io.opentelemetry.opamp.client.internal.response.Response; +import io.opentelemetry.opamp.client.request.service.RequestService; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.time.Duration; From 901e64ba443afb79b94393b7eebe1842b509eaf1 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 12 Sep 2025 08:16:48 -0700 Subject: [PATCH 278/371] Use assertj consistently everywhere (#2252) --- .../disk/buffering/IntegrationTest.java | 14 ++-- .../LogRecordDataDeserializerTest.java | 12 ++- .../MetricDataDeserializerTest.java | 12 ++- .../SpanDataDeserializerTest.java | 12 ++- .../mapping/common/AttributesMapperTest.java | 6 +- .../mapping/common/ResourceMapperTest.java | 5 +- .../mapping/logs/LogRecordDataMapperTest.java | 7 +- .../mapping/logs/ProtoLogsDataMapperTest.java | 39 +++++----- .../mapping/metrics/MetricDataMapperTest.java | 8 +- .../metrics/ProtoMetricsDataMapperTest.java | 31 ++++---- .../spans/ProtoSpansDataMapperTest.java | 31 ++++---- .../mapping/spans/SpanDataMapperTest.java | 19 +++-- .../internal/storage/FolderManagerTest.java | 76 +++++++++---------- .../internal/storage/StorageTest.java | 21 +++-- .../storage/files/ReadableFileTest.java | 60 +++++++-------- .../storage/files/WritableFileTest.java | 36 ++++----- 16 files changed, 185 insertions(+), 204 deletions(-) diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java index 68afb3cbf..ee4deb57a 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java @@ -6,9 +6,7 @@ package io.opentelemetry.contrib.disk.buffering; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -168,20 +166,20 @@ void verifyLogRecordsIntegration() throws IOException { private void assertExporter(FromDiskExporterImpl exporter, Supplier finishedItems) throws IOException { // Verify no data has been received in the original exporter until this point. - assertEquals(0, finishedItems.get()); + assertThat(finishedItems.get()).isEqualTo(0); // Go to the future when we can read the stored items. fastForwardTimeByMillis(storageConfig.getMinFileAgeForReadMillis()); // Read and send stored data. - assertTrue(exporter.exportStoredBatch(1, TimeUnit.SECONDS)); + assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isTrue(); // Now the data must have been delegated to the original exporter. - assertEquals(1, finishedItems.get()); + assertThat(finishedItems.get()).isEqualTo(1); // Bonus: Try to read again, no more data should be available. - assertFalse(exporter.exportStoredBatch(1, TimeUnit.SECONDS)); - assertEquals(1, finishedItems.get()); + assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isFalse(); + assertThat(finishedItems.get()).isEqualTo(1); } @SuppressWarnings("DirectInvocationOnMock") diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java index e508a34c9..a428956a7 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializerTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; @@ -39,16 +39,14 @@ void verifyDeserialization() { @Test void whenDecodingMalformedMessage_wrapIntoDeserializationException() { - assertThrows( - DeserializationException.class, - () -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())); + assertThatThrownBy(() -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())) + .isInstanceOf(DeserializationException.class); } @Test void whenDecodingTooShortMessage_wrapIntoDeserializationException() { - assertThrows( - DeserializationException.class, - () -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())); + assertThatThrownBy(() -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())) + .isInstanceOf(DeserializationException.class); } @Override diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java index 53751e678..7364d865f 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializerTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.testutils.BaseSignalSerializerTest; @@ -17,16 +17,14 @@ class MetricDataDeserializerTest extends BaseSignalSerializerTest { @Test void whenDecodingMalformedMessage_wrapIntoDeserializationException() { - assertThrows( - DeserializationException.class, - () -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())); + assertThatThrownBy(() -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())) + .isInstanceOf(DeserializationException.class); } @Test void whenDecodingTooShortMessage_wrapIntoDeserializationException() { - assertThrows( - DeserializationException.class, - () -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())); + assertThatThrownBy(() -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())) + .isInstanceOf(DeserializationException.class); } @Override diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java index d710ca16e..1b9c61874 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializerTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; @@ -44,16 +44,14 @@ void verifyDeserialization() { @Test void whenDecodingMalformedMessage_wrapIntoDeserializationException() { - assertThrows( - DeserializationException.class, - () -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())); + assertThatThrownBy(() -> getDeserializer().deserialize(TestData.makeMalformedSignalBinary())) + .isInstanceOf(DeserializationException.class); } @Test void whenDecodingTooShortMessage_wrapIntoDeserializationException() { - assertThrows( - DeserializationException.class, - () -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())); + assertThatThrownBy(() -> getDeserializer().deserialize(TestData.makeTooShortSignalBinary())) + .isInstanceOf(DeserializationException.class); } @Override diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java index 2857cf8da..ceb758642 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.common; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; @@ -28,7 +28,7 @@ void verifyMapping() { List proto = mapToProto(attributes); - assertEquals(attributes, mapFromProto(proto)); + assertThat(mapFromProto(proto)).isEqualTo(attributes); } @Test @@ -45,7 +45,7 @@ void verifyArrayMapping() { List serialized = mapToProto(attributes); - assertEquals(attributes, mapFromProto(serialized)); + assertThat(mapFromProto(serialized)).isEqualTo(attributes); } private static List mapToProto(Attributes attributes) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ResourceMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ResourceMapperTest.java index e9feb8be4..9776cd068 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ResourceMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/ResourceMapperTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.common; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.contrib.disk.buffering.testutils.TestData; import io.opentelemetry.proto.resource.v1.Resource; @@ -17,7 +17,8 @@ class ResourceMapperTest { void verifyMapping() { Resource proto = mapToProto(TestData.RESOURCE_FULL); - assertEquals(TestData.RESOURCE_FULL, mapToSdk(proto, TestData.RESOURCE_FULL.getSchemaUrl())); + assertThat(mapToSdk(proto, TestData.RESOURCE_FULL.getSchemaUrl())) + .isEqualTo(TestData.RESOURCE_FULL); } private static Resource mapToProto(io.opentelemetry.sdk.resources.Resource sdkResource) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java index 0041c61bf..56710355f 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/LogRecordDataMapperTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; @@ -38,9 +38,8 @@ class LogRecordDataMapperTest { void verifyMapping() { LogRecord proto = mapToProto(LOG_RECORD); - assertEquals( - LOG_RECORD, - mapToSdk(proto, LOG_RECORD.getResource(), LOG_RECORD.getInstrumentationScopeInfo())); + assertThat(mapToSdk(proto, LOG_RECORD.getResource(), LOG_RECORD.getInstrumentationScopeInfo())) + .isEqualTo(LOG_RECORD); } private static LogRecord mapToProto(LogRecordData data) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java index 9a5d93cd8..7242bd65e 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/logs/ProtoLogsDataMapperTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import io.opentelemetry.api.common.Value; import io.opentelemetry.api.logs.Severity; @@ -107,9 +106,9 @@ void verifyConversionDataStructure() { ExportLogsServiceRequest result = mapToProto(signals); List resourceLogsList = result.resource_logs; - assertEquals(1, resourceLogsList.size()); - assertEquals(1, resourceLogsList.get(0).scope_logs.size()); - assertEquals(1, resourceLogsList.get(0).scope_logs.get(0).log_records.size()); + assertThat(resourceLogsList).hasSize(1); + assertThat(resourceLogsList.get(0).scope_logs).hasSize(1); + assertThat(resourceLogsList.get(0).scope_logs.get(0).log_records).hasSize(1); assertThat(mapFromProto(result)).containsExactlyInAnyOrderElementsOf(signals); } @@ -121,15 +120,15 @@ void verifyMultipleLogsWithSameResourceAndScope() { ExportLogsServiceRequest proto = mapToProto(signals); List resourceLogsList = proto.resource_logs; - assertEquals(1, resourceLogsList.size()); + assertThat(resourceLogsList).hasSize(1); List scopeLogsList = resourceLogsList.get(0).scope_logs; - assertEquals(1, scopeLogsList.size()); + assertThat(scopeLogsList).hasSize(1); List logRecords = scopeLogsList.get(0).log_records; - assertEquals(2, logRecords.size()); - assertEquals("Log body", logRecords.get(0).body.string_value); - assertEquals("Other log body", logRecords.get(1).body.string_value); + assertThat(logRecords).hasSize(2); + assertThat(logRecords.get(0).body.string_value).isEqualTo("Log body"); + assertThat(logRecords.get(1).body.string_value).isEqualTo("Other log body"); - assertEquals(2, mapFromProto(proto).size()); + assertThat(mapFromProto(proto)).hasSize(2); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } @@ -142,15 +141,15 @@ void verifyMultipleLogsWithSameResourceDifferentScope() { ExportLogsServiceRequest proto = mapToProto(signals); List resourceLogsList = proto.resource_logs; - assertEquals(1, resourceLogsList.size()); + assertThat(resourceLogsList).hasSize(1); List scopeLogsList = resourceLogsList.get(0).scope_logs; - assertEquals(2, scopeLogsList.size()); + assertThat(scopeLogsList).hasSize(2); ScopeLogs firstScope = scopeLogsList.get(0); ScopeLogs secondScope = scopeLogsList.get(1); List firstScopeLogs = firstScope.log_records; List secondScopeLogs = secondScope.log_records; - assertEquals(1, firstScopeLogs.size()); - assertEquals(1, secondScopeLogs.size()); + assertThat(firstScopeLogs).hasSize(1); + assertThat(secondScopeLogs).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } @@ -162,19 +161,19 @@ void verifyMultipleLogsWithDifferentResource() { ExportLogsServiceRequest proto = mapToProto(signals); List resourceLogsList = proto.resource_logs; - assertEquals(2, resourceLogsList.size()); + assertThat(resourceLogsList).hasSize(2); ResourceLogs firstResourceLogs = resourceLogsList.get(0); ResourceLogs secondResourceLogs = resourceLogsList.get(1); List firstScopeLogsList = firstResourceLogs.scope_logs; List secondScopeLogsList = secondResourceLogs.scope_logs; - assertEquals(1, firstScopeLogsList.size()); - assertEquals(1, secondScopeLogsList.size()); + assertThat(firstScopeLogsList).hasSize(1); + assertThat(secondScopeLogsList).hasSize(1); ScopeLogs firstScope = firstScopeLogsList.get(0); ScopeLogs secondScope = secondScopeLogsList.get(0); List firstScopeLogs = firstScope.log_records; List secondScopeLogs = secondScope.log_records; - assertEquals(1, firstScopeLogs.size()); - assertEquals(1, secondScopeLogs.size()); + assertThat(firstScopeLogs).hasSize(1); + assertThat(secondScopeLogs).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } @@ -188,7 +187,7 @@ void verifyLogWithEventName() { List resourceLogsList = result.resource_logs; LogRecord firstLog = resourceLogsList.get(0).scope_logs.get(0).log_records.get(0); - assertEquals("test.event.name", firstLog.event_name); + assertThat(firstLog.event_name).isEqualTo("test.event.name"); assertThat(mapFromProto(result)).containsExactlyInAnyOrderElementsOf(signals); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapperTest.java index b4f7f64d9..b5e804e29 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/MetricDataMapperTest.java @@ -9,7 +9,6 @@ import static io.opentelemetry.contrib.disk.buffering.testutils.TestData.makeLongGauge; import static io.opentelemetry.contrib.disk.buffering.testutils.TestData.makeLongPointData; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceFlags; @@ -121,9 +120,10 @@ void verifySummaryMapping() { Metric proto = mapToProto(summaryMetric); - assertEquals( - summaryMetric, - mapToSdk(proto, summaryMetric.getResource(), summaryMetric.getInstrumentationScopeInfo())); + assertThat( + mapToSdk( + proto, summaryMetric.getResource(), summaryMetric.getInstrumentationScopeInfo())) + .isEqualTo(summaryMetric); } @Test diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java index 59d369704..f114fe04d 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/metrics/ProtoMetricsDataMapperTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.metrics; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import io.opentelemetry.api.trace.TraceFlags; import io.opentelemetry.contrib.disk.buffering.testutils.TestData; @@ -33,9 +32,9 @@ void verifyConversionDataStructure() { ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; - assertEquals(1, resourceMetrics.size()); - assertEquals(1, resourceMetrics.get(0).scope_metrics.size()); - assertEquals(1, resourceMetrics.get(0).scope_metrics.get(0).metrics.size()); + assertThat(resourceMetrics).hasSize(1); + assertThat(resourceMetrics.get(0).scope_metrics).hasSize(1); + assertThat(resourceMetrics.get(0).scope_metrics.get(0).metrics).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(expectedSignals); } @@ -52,11 +51,11 @@ void verifyMultipleMetricsWithSameResourceAndScope() { ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; - assertEquals(1, resourceMetrics.size()); + assertThat(resourceMetrics).hasSize(1); List scopeMetrics = resourceMetrics.get(0).scope_metrics; - assertEquals(1, scopeMetrics.size()); + assertThat(scopeMetrics).hasSize(1); List metrics = scopeMetrics.get(0).metrics; - assertEquals(2, metrics.size()); + assertThat(metrics).hasSize(2); List result = mapFromProto(proto); @@ -81,15 +80,15 @@ void verifyMultipleMetricsWithSameResourceDifferentScope() { ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; - assertEquals(1, resourceMetrics.size()); + assertThat(resourceMetrics).hasSize(1); List scopeMetrics = resourceMetrics.get(0).scope_metrics; - assertEquals(2, scopeMetrics.size()); + assertThat(scopeMetrics).hasSize(2); ScopeMetrics firstScope = scopeMetrics.get(0); ScopeMetrics secondScope = scopeMetrics.get(1); List firstScopeMetrics = firstScope.metrics; List secondScopeMetrics = secondScope.metrics; - assertEquals(1, firstScopeMetrics.size()); - assertEquals(1, secondScopeMetrics.size()); + assertThat(firstScopeMetrics).hasSize(1); + assertThat(secondScopeMetrics).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(expectedSignals); } @@ -116,19 +115,19 @@ void verifyMultipleMetricsWithDifferentResource() { ExportMetricsServiceRequest proto = mapToProto(signals); List resourceMetrics = proto.resource_metrics; - assertEquals(2, resourceMetrics.size()); + assertThat(resourceMetrics).hasSize(2); ResourceMetrics firstResourceMetrics = resourceMetrics.get(0); ResourceMetrics secondResourceMetrics = resourceMetrics.get(1); List firstScopeMetrics = firstResourceMetrics.scope_metrics; List secondScopeMetrics = secondResourceMetrics.scope_metrics; - assertEquals(1, firstScopeMetrics.size()); - assertEquals(1, secondScopeMetrics.size()); + assertThat(firstScopeMetrics).hasSize(1); + assertThat(secondScopeMetrics).hasSize(1); ScopeMetrics firstScope = firstScopeMetrics.get(0); ScopeMetrics secondScope = secondScopeMetrics.get(0); List firstMetrics = firstScope.metrics; List secondMetrics = secondScope.metrics; - assertEquals(1, firstMetrics.size()); - assertEquals(1, secondMetrics.size()); + assertThat(firstMetrics).hasSize(1); + assertThat(secondMetrics).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(expectedSignals); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java index ca325496b..910455463 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/ProtoSpansDataMapperTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; @@ -119,9 +118,9 @@ void verifyConversionDataStructure() { ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; - assertEquals(1, resourceSpans.size()); - assertEquals(1, resourceSpans.get(0).scope_spans.size()); - assertEquals(1, resourceSpans.get(0).scope_spans.get(0).spans.size()); + assertThat(resourceSpans).hasSize(1); + assertThat(resourceSpans.get(0).scope_spans).hasSize(1); + assertThat(resourceSpans.get(0).scope_spans.get(0).spans).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } @@ -133,11 +132,11 @@ void verifyMultipleSpansWithSameResourceAndScope() { ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; - assertEquals(1, resourceSpans.size()); + assertThat(resourceSpans).hasSize(1); List scopeSpans = resourceSpans.get(0).scope_spans; - assertEquals(1, scopeSpans.size()); + assertThat(scopeSpans).hasSize(1); List spans = scopeSpans.get(0).spans; - assertEquals(2, spans.size()); + assertThat(spans).hasSize(2); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } @@ -149,15 +148,15 @@ void verifyMultipleSpansWithSameResourceDifferentScope() { ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; - assertEquals(1, resourceSpans.size()); + assertThat(resourceSpans).hasSize(1); List scopeSpans = resourceSpans.get(0).scope_spans; - assertEquals(2, scopeSpans.size()); + assertThat(scopeSpans).hasSize(2); ScopeSpans firstScope = scopeSpans.get(0); ScopeSpans secondScope = scopeSpans.get(1); List firstScopeSpans = firstScope.spans; List secondScopeSpans = secondScope.spans; - assertEquals(1, firstScopeSpans.size()); - assertEquals(1, secondScopeSpans.size()); + assertThat(firstScopeSpans).hasSize(1); + assertThat(secondScopeSpans).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } @@ -169,19 +168,19 @@ void verifyMultipleSpansWithDifferentResource() { ExportTraceServiceRequest proto = mapToProto(signals); List resourceSpans = proto.resource_spans; - assertEquals(2, resourceSpans.size()); + assertThat(resourceSpans).hasSize(2); ResourceSpans firstResourceSpans = resourceSpans.get(0); ResourceSpans secondResourceSpans = resourceSpans.get(1); List firstScopeSpans = firstResourceSpans.scope_spans; List secondScopeSpans = secondResourceSpans.scope_spans; - assertEquals(1, firstScopeSpans.size()); - assertEquals(1, secondScopeSpans.size()); + assertThat(firstScopeSpans).hasSize(1); + assertThat(secondScopeSpans).hasSize(1); ScopeSpans firstScope = firstScopeSpans.get(0); ScopeSpans secondScope = secondScopeSpans.get(0); List firstSpans = firstScope.spans; List secondSpans = secondScope.spans; - assertEquals(1, firstSpans.size()); - assertEquals(1, secondSpans.size()); + assertThat(firstSpans).hasSize(1); + assertThat(secondSpans).hasSize(1); assertThat(mapFromProto(proto)).containsExactlyInAnyOrderElementsOf(signals); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/SpanDataMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/SpanDataMapperTest.java index de8f8ff78..43394431f 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/SpanDataMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/spans/SpanDataMapperTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; @@ -74,21 +74,20 @@ class SpanDataMapperTest { void verifyMapping() { Span proto = mapToProto(SPAN_DATA); - assertEquals( - SPAN_DATA, - mapToSdk(proto, SPAN_DATA.getResource(), SPAN_DATA.getInstrumentationScopeInfo())); + assertThat(mapToSdk(proto, SPAN_DATA.getResource(), SPAN_DATA.getInstrumentationScopeInfo())) + .isEqualTo(SPAN_DATA); } @Test void verifyMappingWithTraceState() { Span proto = mapToProto(SPAN_DATA_WITH_TRACE_STATE); - assertEquals( - SPAN_DATA_WITH_TRACE_STATE, - mapToSdk( - proto, - SPAN_DATA_WITH_TRACE_STATE.getResource(), - SPAN_DATA_WITH_TRACE_STATE.getInstrumentationScopeInfo())); + assertThat( + mapToSdk( + proto, + SPAN_DATA_WITH_TRACE_STATE.getResource(), + SPAN_DATA_WITH_TRACE_STATE.getInstrumentationScopeInfo())) + .isEqualTo(SPAN_DATA_WITH_TRACE_STATE); } private static Span mapToProto(SpanData source) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java index ad994c38d..d8a95a9cc 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java @@ -9,12 +9,8 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_SIZE; import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MIN_FILE_AGE_FOR_READ_MILLIS; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -52,7 +48,7 @@ void createWritableFile_withTimeMillisAsName() throws IOException { when(clock.now()).thenReturn(MILLISECONDS.toNanos(1000L)); WritableFile file = folderManager.createWritableFile(); - assertEquals("1000", file.getFile().getName()); + assertThat(file.getFile().getName()).isEqualTo("1000"); } @Test @@ -69,12 +65,12 @@ void createWritableFile_andRemoveOldestOne_whenTheAvailableFolderSpaceIsNotEnoug WritableFile file = folderManager.createWritableFile(); - assertNotEquals(existingFile1, file.getFile()); - assertNotEquals(existingFile2, file.getFile()); - assertNotEquals(existingFile3, file.getFile()); - assertTrue(existingFile2.exists()); - assertTrue(existingFile3.exists()); - assertFalse(existingFile1.exists()); + assertThat(file.getFile()).isNotEqualTo(existingFile1); + assertThat(file.getFile()).isNotEqualTo(existingFile2); + assertThat(file.getFile()).isNotEqualTo(existingFile3); + assertThat(existingFile2.exists()).isTrue(); + assertThat(existingFile3.exists()).isTrue(); + assertThat(existingFile1.exists()).isFalse(); } @Test @@ -91,8 +87,8 @@ void closeCurrentlyWritableFile_whenItIsReadyToBeRead_andNoOtherReadableFilesAre ReadableFile readableFile = folderManager.getReadableFile(); - assertEquals(writableFile.getFile(), readableFile.getFile()); - assertTrue(writableFile.isClosed()); + assertThat(readableFile.getFile()).isEqualTo(writableFile.getFile()); + assertThat(writableFile.isClosed()).isTrue(); } @Test @@ -109,14 +105,14 @@ void closeCurrentlyWritableFile_whenItIsReadyToBeRead_andNoOtherReadableFilesAre when(clock.now()).thenReturn(MILLISECONDS.toNanos(1000L + MIN_FILE_AGE_FOR_READ_MILLIS)); ReadableFile readableFile = folderManager.getReadableFile(); - assertEquals(existingFile1, readableFile.getFile()); + assertThat(readableFile.getFile()).isEqualTo(existingFile1); folderManager.createWritableFile(); - assertTrue(existingFile2.exists()); - assertTrue(existingFile3.exists()); - assertFalse(existingFile1.exists()); - assertTrue(readableFile.isClosed()); + assertThat(existingFile2.exists()).isTrue(); + assertThat(existingFile3.exists()).isTrue(); + assertThat(existingFile1.exists()).isFalse(); + assertThat(readableFile.isClosed()).isTrue(); } @Test @@ -133,12 +129,12 @@ void createWritableFile_andDoNotRemoveOldestOne_ifAtLeastOneExpiredFileIsPurged( WritableFile file = folderManager.createWritableFile(); - assertNotEquals(existingFile1, file.getFile()); - assertNotEquals(existingFile2, file.getFile()); - assertNotEquals(existingFile3, file.getFile()); - assertTrue(existingFile2.exists()); - assertTrue(existingFile1.exists()); - assertFalse(existingFile3.exists()); + assertThat(file.getFile()).isNotEqualTo(existingFile1); + assertThat(file.getFile()).isNotEqualTo(existingFile2); + assertThat(file.getFile()).isNotEqualTo(existingFile3); + assertThat(existingFile2.exists()).isTrue(); + assertThat(existingFile1.exists()).isTrue(); + assertThat(existingFile3.exists()).isFalse(); } @Test @@ -152,9 +148,9 @@ void purgeExpiredForReadFiles_whenCreatingNewOne() throws IOException { WritableFile file = folderManager.createWritableFile(); - assertFalse(expiredReadableFile.exists()); - assertTrue(expiredWritableFile.exists()); - assertNotEquals(expiredWritableFile, file.getFile()); + assertThat(expiredReadableFile.exists()).isFalse(); + assertThat(expiredWritableFile.exists()).isTrue(); + assertThat(file.getFile()).isNotEqualTo(expiredWritableFile); } @Test @@ -167,17 +163,17 @@ void closeExpiredReadableFileInUseIfAny_whenPurgingExpiredForReadFiles_whenCreat when(clock.now()).thenReturn(MILLISECONDS.toNanos(900 + MIN_FILE_AGE_FOR_READ_MILLIS)); ReadableFile readableFile = folderManager.getReadableFile(); - assertEquals(expiredReadableFileBeingRead, readableFile.getFile()); + assertThat(readableFile.getFile()).isEqualTo(expiredReadableFileBeingRead); when(clock.now()).thenReturn(MILLISECONDS.toNanos(11_500L)); WritableFile file = folderManager.createWritableFile(); - assertFalse(expiredReadableFile.exists()); - assertFalse(expiredReadableFileBeingRead.exists()); - assertTrue(expiredWritableFile.exists()); - assertNotEquals(expiredWritableFile, file.getFile()); - assertTrue(readableFile.isClosed()); + assertThat(expiredReadableFile.exists()).isFalse(); + assertThat(expiredReadableFileBeingRead.exists()).isFalse(); + assertThat(expiredWritableFile.exists()).isTrue(); + assertThat(file.getFile()).isNotEqualTo(expiredWritableFile); + assertThat(readableFile.isClosed()).isTrue(); } @Test @@ -192,7 +188,7 @@ void provideFileForRead_afterItsMinFileAgeForReadTimePassed() throws IOException ReadableFile file = folderManager.getReadableFile(); - assertEquals(readableFile, file.getFile()); + assertThat(file.getFile()).isEqualTo(readableFile); } @Test @@ -209,12 +205,12 @@ void provideOldestFileForRead_whenMultipleReadableFilesAreAvailable() throws IOE ReadableFile file = folderManager.getReadableFile(); - assertEquals(readableFileOlder, file.getFile()); + assertThat(file.getFile()).isEqualTo(readableFileOlder); } @Test void provideNullFileForRead_whenNoFilesAreAvailable() throws IOException { - assertNull(folderManager.getReadableFile()); + assertThat(folderManager.getReadableFile()).isNull(); } @Test @@ -223,7 +219,7 @@ void provideNullFileForRead_whenOnlyReadableFilesAreAvailable() throws IOExcepti File writableFile = new File(rootDir, String.valueOf(currentTime)); createFiles(writableFile); - assertNull(folderManager.getReadableFile()); + assertThat(folderManager.getReadableFile()).isNull(); } @Test @@ -234,7 +230,7 @@ void provideNullFileForRead_whenReadableFilesAreExpired() throws IOException { createFiles(expiredReadableFile1, expiredReadableFile2); when(clock.now()).thenReturn(creationReferenceTime + MAX_FILE_AGE_FOR_READ_MILLIS); - assertNull(folderManager.getReadableFile()); + assertThat(folderManager.getReadableFile()).isNull(); } private static void fillWithBytes(File file, int size) throws IOException { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java index d96b9a1bc..b33387a8c 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java @@ -6,9 +6,8 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage; import static io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult.TRY_LATER; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; @@ -58,7 +57,7 @@ void tearDown() throws IOException { void whenReadingAndProcessingSuccessfully_returnSuccess() throws IOException { when(folderManager.getReadableFile()).thenReturn(readableFile); - assertEquals(ReadableResult.SUCCEEDED, storage.readAndProcess(processing)); + assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.SUCCEEDED); verify(readableFile).readAndProcess(processing); } @@ -68,7 +67,7 @@ void whenReadableFileProcessingFails_returnTryLater() throws IOException { when(folderManager.getReadableFile()).thenReturn(readableFile); when(readableFile.readAndProcess(processing)).thenReturn(TRY_LATER); - assertEquals(TRY_LATER, storage.readAndProcess(processing)); + assertThat(storage.readAndProcess(processing)).isEqualTo(TRY_LATER); verify(readableFile).readAndProcess(processing); } @@ -78,8 +77,8 @@ void whenReadingMultipleTimes_reuseReader() throws IOException { ReadableFile anotherReadable = mock(); when(folderManager.getReadableFile()).thenReturn(readableFile).thenReturn(anotherReadable); - assertEquals(ReadableResult.SUCCEEDED, storage.readAndProcess(processing)); - assertEquals(ReadableResult.SUCCEEDED, storage.readAndProcess(processing)); + assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.SUCCEEDED); + assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.SUCCEEDED); verify(readableFile, times(2)).readAndProcess(processing); verify(folderManager, times(1)).getReadableFile(); @@ -103,18 +102,18 @@ void whenWritingMultipleTimes_reuseWriter() throws IOException { @Test void whenAttemptingToReadAfterClosed_returnFailed() throws IOException { storage.close(); - assertEquals(ReadableResult.FAILED, storage.readAndProcess(processing)); + assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.FAILED); } @Test void whenAttemptingToWriteAfterClosed_returnFalse() throws IOException { storage.close(); - assertFalse(storage.write(new ByteArraySerializer(new byte[1]))); + assertThat(storage.write(new ByteArraySerializer(new byte[1]))).isFalse(); } @Test void whenNoFileAvailableForReading_returnFailed() throws IOException { - assertEquals(ReadableResult.FAILED, storage.readAndProcess(processing)); + assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.FAILED); } @Test @@ -152,7 +151,7 @@ void whenEveryNewFileFoundCannotBeRead_returnContentNotAvailable() throws IOExce when(folderManager.getReadableFile()).thenReturn(readableFile); when(readableFile.readAndProcess(processing)).thenReturn(ReadableResult.FAILED); - assertEquals(ReadableResult.FAILED, storage.readAndProcess(processing)); + assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.FAILED); verify(folderManager, times(3)).getReadableFile(); } @@ -215,7 +214,7 @@ void whenEveryAttemptToWriteFails_returnFalse() throws IOException { when(folderManager.createWritableFile()).thenReturn(writableFile); when(writableFile.append(data)).thenReturn(WritableResult.FAILED); - assertFalse(storage.write(data)); + assertThat(storage.write(data)).isFalse(); verify(folderManager, times(3)).createWritableFile(); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java index a9d0eb5da..8fdc1d41e 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java @@ -8,10 +8,8 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_AGE_FOR_READ_MILLIS; import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.getConfiguration; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -120,27 +118,27 @@ private static void addFileContents(File source) throws IOException { void readSingleItemAndRemoveIt() throws IOException { readableFile.readAndProcess( bytes -> { - assertEquals(FIRST_LOG_RECORD, deserialize(bytes)); + assertThat(deserialize(bytes)).isEqualTo(FIRST_LOG_RECORD); return ProcessResult.SUCCEEDED; }); List logs = getRemainingDataAndClose(readableFile); - assertEquals(2, logs.size()); - assertEquals(SECOND_LOG_RECORD, logs.get(0)); - assertEquals(THIRD_LOG_RECORD, logs.get(1)); + assertThat(logs.size()).isEqualTo(2); + assertThat(logs.get(0)).isEqualTo(SECOND_LOG_RECORD); + assertThat(logs.get(1)).isEqualTo(THIRD_LOG_RECORD); } @Test void whenProcessingSucceeds_returnSuccessStatus() throws IOException { - assertEquals( - ReadableResult.SUCCEEDED, readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)); + assertThat(readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) + .isEqualTo(ReadableResult.SUCCEEDED); } @Test void whenProcessingFails_returnTryLaterStatus() throws IOException { - assertEquals( - ReadableResult.TRY_LATER, readableFile.readAndProcess(bytes -> ProcessResult.TRY_LATER)); + assertThat(readableFile.readAndProcess(bytes -> ProcessResult.TRY_LATER)) + .isEqualTo(ReadableResult.TRY_LATER); } @Test @@ -158,8 +156,8 @@ void readMultipleLinesAndRemoveThem() throws IOException { List logs = getRemainingDataAndClose(readableFile); - assertEquals(1, logs.size()); - assertEquals(THIRD_LOG_RECORD, logs.get(0)); + assertThat(logs.size()).isEqualTo(1); + assertThat(logs.get(0)).isEqualTo(THIRD_LOG_RECORD); } @Test @@ -168,24 +166,24 @@ void whenConsumerReturnsFalse_doNotRemoveLineFromSource() throws IOException { List logs = getRemainingDataAndClose(readableFile); - assertEquals(3, logs.size()); + assertThat(logs.size()).isEqualTo(3); } @Test void whenReadingLastLine_deleteOriginalFile_and_close() throws IOException { getRemainingDataAndClose(readableFile); - assertFalse(source.exists()); - assertTrue(readableFile.isClosed()); + assertThat(source.exists()).isFalse(); + assertThat(readableFile.isClosed()).isTrue(); } @Test void whenTheFileContentIsInvalid_deleteOriginalFile_and_close() throws IOException { - assertEquals( - ReadableResult.FAILED, readableFile.readAndProcess(bytes -> ProcessResult.CONTENT_INVALID)); + assertThat(readableFile.readAndProcess(bytes -> ProcessResult.CONTENT_INVALID)) + .isEqualTo(ReadableResult.FAILED); - assertFalse(source.exists()); - assertTrue(readableFile.isClosed()); + assertThat(source.exists()).isFalse(); + assertThat(readableFile.isClosed()).isTrue(); } @Test @@ -199,11 +197,11 @@ void whenNoMoreLinesAvailableToRead_deleteOriginalFile_close_and_returnNoContent ReadableFile emptyReadableFile = new ReadableFile(emptyFile, CREATED_TIME_MILLIS, clock, getConfiguration(dir)); - assertEquals( - ReadableResult.FAILED, emptyReadableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)); + assertThat(emptyReadableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) + .isEqualTo(ReadableResult.FAILED); - assertTrue(emptyReadableFile.isClosed()); - assertFalse(emptyFile.exists()); + assertThat(emptyReadableFile.isClosed()).isTrue(); + assertThat(emptyFile.exists()).isFalse(); } @Test @@ -214,10 +212,10 @@ void whenNoMoreLinesAvailableToRead_deleteOriginalFile_close_and_returnNoContent when(clock.now()) .thenReturn(MILLISECONDS.toNanos(CREATED_TIME_MILLIS + MAX_FILE_AGE_FOR_READ_MILLIS)); - assertEquals( - ReadableResult.FAILED, readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)); + assertThat(readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) + .isEqualTo(ReadableResult.FAILED); - assertTrue(readableFile.isClosed()); + assertThat(readableFile.isClosed()).isTrue(); } @Test @@ -225,14 +223,14 @@ void whenReadingAfterClosed_returnFailedStatus() throws IOException { readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED); readableFile.close(); - assertEquals( - ReadableResult.FAILED, readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)); + assertThat(readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) + .isEqualTo(ReadableResult.FAILED); } private static void assertDeserializedData(LogRecordData expected, byte[] bytes) { try { List deserialized = DESERIALIZER.deserialize(bytes); - assertEquals(expected, deserialized.get(0)); + assertThat(deserialized.get(0)).isEqualTo(expected); } catch (DeserializationException e) { throw new RuntimeException(e); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java index cae1e9f64..91ec94f09 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java @@ -8,9 +8,7 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_AGE_FOR_WRITE_MILLIS; import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_SIZE; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -58,14 +56,14 @@ void tearDown() throws IOException { void hasNotExpired_whenWriteAgeHasNotExpired() { when(clock.now()).thenReturn(MILLISECONDS.toNanos(1500L)); - assertFalse(writableFile.hasExpired()); + assertThat(writableFile.hasExpired()).isFalse(); } @Test void hasExpired_whenWriteAgeHasExpired() { when(clock.now()).thenReturn(MILLISECONDS.toNanos(2000L)); - assertTrue(writableFile.hasExpired()); + assertThat(writableFile.hasExpired()).isTrue(); } @Test @@ -78,22 +76,22 @@ void appendDataInNewLines_andIncreaseSize() throws IOException { List lines = getWrittenLines(); - assertEquals(2, lines.size()); - assertEquals("First line", lines.get(0)); - assertEquals("Second line", lines.get(1)); - assertEquals(line1.length + line2.length, writableFile.getSize()); + assertThat(lines).hasSize(2); + assertThat(lines.get(0)).isEqualTo("First line"); + assertThat(lines.get(1)).isEqualTo("Second line"); + assertThat(writableFile.getSize()).isEqualTo(line1.length + line2.length); } @Test void whenAppendingData_andNotEnoughSpaceIsAvailable_closeAndReturnFailed() throws IOException { - assertEquals( - WritableResult.SUCCEEDED, - writableFile.append(new ByteArraySerializer(new byte[MAX_FILE_SIZE]))); + assertThat(writableFile.append(new ByteArraySerializer(new byte[MAX_FILE_SIZE]))) + .isEqualTo(WritableResult.SUCCEEDED); - assertEquals(WritableResult.FAILED, writableFile.append(new ByteArraySerializer(new byte[1]))); + assertThat(writableFile.append(new ByteArraySerializer(new byte[1]))) + .isEqualTo(WritableResult.FAILED); - assertEquals(1, getWrittenLines().size()); - assertEquals(MAX_FILE_SIZE, writableFile.getSize()); + assertThat(getWrittenLines()).hasSize(1); + assertThat(writableFile.getSize()).isEqualTo(MAX_FILE_SIZE); } @Test @@ -102,9 +100,10 @@ void whenAppendingData_andHasExpired_closeAndReturnExpiredStatus() throws IOExce when(clock.now()) .thenReturn(MILLISECONDS.toNanos(CREATED_TIME_MILLIS + MAX_FILE_AGE_FOR_WRITE_MILLIS)); - assertEquals(WritableResult.FAILED, writableFile.append(new ByteArraySerializer(new byte[1]))); + assertThat(writableFile.append(new ByteArraySerializer(new byte[1]))) + .isEqualTo(WritableResult.FAILED); - assertEquals(1, getWrittenLines().size()); + assertThat(getWrittenLines()).hasSize(1); } @Test @@ -112,7 +111,8 @@ void whenAppendingData_andIsAlreadyClosed_returnFailedStatus() throws IOExceptio writableFile.append(new ByteArraySerializer(new byte[1])); writableFile.close(); - assertEquals(WritableResult.FAILED, writableFile.append(new ByteArraySerializer(new byte[2]))); + assertThat(writableFile.append(new ByteArraySerializer(new byte[2]))) + .isEqualTo(WritableResult.FAILED); } private static byte[] getByteArrayLine(String line) { From ca98ba3229c6af163b511385e1b8cd34eb543e91 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Fri, 12 Sep 2025 11:42:04 -0400 Subject: [PATCH 279/371] Add note to copilot instructions around gradle 9 tests (#2256) --- .github/copilot-instructions.md | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 9b11e2264..d7f693363 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -29,6 +29,33 @@ This repository provides observability instrumentation for Java applications. - Resource cleanup and lifecycle management - Comprehensive unit tests for new functionality +### Test suites + +This project uses gradle 9 which requires specifying test classes and paths explicitly. + +For example, this will NOT work because it registers a `Test` without specifying the test classes or paths: + +```kotlin +tasks.register("IntegrationTestUserCreds") { + dependsOn(tasks.shadowJar) + dependsOn(tasks.named("copyAgent")) + ... +} +``` + +This is fixed by specifying the test classes and classpath explicitly: + +```kotlin +tasks.register("IntegrationTestUserCreds") { + testClassesDirs = sourceSets.test.get().output.classesDirs + classpath = sourceSets.test.get().runtimeClasspath + + dependsOn(tasks.shadowJar) + dependsOn(tasks.named("copyAgent")) + ... +} +``` + ## Coding Agent Instructions When implementing changes or new features: From dae84103962fa39172bc90d2b8162a4e0e2ff840 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 12 Sep 2025 09:09:54 -0700 Subject: [PATCH 280/371] Use assertj consistently everywhere (#2254) --- ...toConfigurationCustomizerProviderTest.java | 33 +++++++++---------- .../auth/GcpAuthExtensionEndToEndTest.java | 25 +++++++------- .../service/HttpRequestServiceTest.java | 2 +- .../service/WebSocketRequestServiceTest.java | 2 +- 4 files changed, 28 insertions(+), 34 deletions(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index a4e7712c1..d7a4f07fd 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -11,9 +11,7 @@ import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_METRICS; import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_TRACES; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -165,12 +163,12 @@ void testTraceCustomizerOtlpHttp() { generateTestSpan(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); - assertTrue(joinResult.isSuccess()); + assertThat(joinResult.isSuccess()).isTrue(); Mockito.verify(mockOtlpHttpSpanExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpHttpSpanExporterBuilder, Mockito.times(1)) .setHeaders(traceHeaderSupplierCaptor.capture()); - assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(traceHeaderSupplierCaptor.getValue().get().size()).isEqualTo(2); assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) .isTrue(); @@ -218,12 +216,12 @@ void testTraceCustomizerOtlpGrpc() { generateTestSpan(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); - assertTrue(joinResult.isSuccess()); + assertThat(joinResult.isSuccess()).isTrue(); Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) .setHeaders(traceHeaderSupplierCaptor.capture()); - assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(traceHeaderSupplierCaptor.getValue().get().size()).isEqualTo(2); assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) .isTrue(); @@ -274,12 +272,12 @@ void testMetricCustomizerOtlpHttp() { generateTestMetric(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); - assertTrue(joinResult.isSuccess()); + assertThat(joinResult.isSuccess()).isTrue(); Mockito.verify(mockOtlpHttpMetricExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpHttpMetricExporterBuilder, Mockito.times(1)) .setHeaders(metricHeaderSupplierCaptor.capture()); - assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(metricHeaderSupplierCaptor.getValue().get().size()).isEqualTo(2); assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) .isTrue(); @@ -335,12 +333,12 @@ void testMetricCustomizerOtlpGrpc() { generateTestMetric(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); - assertTrue(joinResult.isSuccess()); + assertThat(joinResult.isSuccess()).isTrue(); Mockito.verify(mockOtlpGrpcMetricExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpGrpcMetricExporterBuilder, Mockito.times(1)) .setHeaders(metricHeaderSupplierCaptor.capture()); - assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(metricHeaderSupplierCaptor.getValue().get().size()).isEqualTo(2); assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) .isTrue(); @@ -378,9 +376,8 @@ void testCustomizerFailWithMissingResourceProject() { .when(GoogleCredentials::getApplicationDefault) .thenReturn(mockedGoogleCredentials); - assertThrows( - ConfigurationException.class, - () -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)); + assertThatThrownBy(() -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter)) + .isInstanceOf(ConfigurationException.class); } } @@ -440,7 +437,7 @@ void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws IOExce generateTestSpan(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); - assertTrue(joinResult.isSuccess()); + assertThat(joinResult.isSuccess()).isTrue(); Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) .setHeaders(traceHeaderSupplierCaptor.capture()); @@ -509,14 +506,14 @@ void testTargetSignalsBehavior(TargetSignalBehavior testCase) { generateTestSpan(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); - assertTrue(joinResult.isSuccess()); + assertThat(joinResult.isSuccess()).isTrue(); // Check Traces modification conditions if (testCase.getExpectedIsTraceSignalModified()) { // If traces signal is expected to be modified, auth headers must be present Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) .setHeaders(traceHeaderSupplierCaptor.capture()); - assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(traceHeaderSupplierCaptor.getValue().get().size()).isEqualTo(2); assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) .isTrue(); } else { @@ -530,7 +527,7 @@ void testTargetSignalsBehavior(TargetSignalBehavior testCase) { // If metrics signal is expected to be modified, auth headers must be present Mockito.verify(spyOtlpGrpcMetricExporterBuilder, Mockito.times(1)) .setHeaders(metricHeaderSupplierCaptor.capture()); - assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(metricHeaderSupplierCaptor.getValue().get().size()).isEqualTo(2); assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) .isTrue(); } else { diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java index 0553007b8..f87762474 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.java @@ -7,9 +7,8 @@ import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.GCP_USER_PROJECT_ID_KEY; import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER; +import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockserver.model.HttpRequest.request; import static org.mockserver.model.HttpResponse.response; import static org.mockserver.stop.Stop.stopQuietly; @@ -160,24 +159,22 @@ public X509Certificate[] getAcceptedIssuers() { private static void verifyResourceAttributes(List extractedResourceSpans) { extractedResourceSpans.forEach( resourceSpan -> - assertTrue( - resourceSpan - .getResource() - .getAttributesList() - .contains( - KeyValue.newBuilder() - .setKey(GCP_USER_PROJECT_ID_KEY) - .setValue(AnyValue.newBuilder().setStringValue(DUMMY_GCP_PROJECT)) - .build()))); + assertThat(resourceSpan.getResource().getAttributesList()) + .contains( + KeyValue.newBuilder() + .setKey(GCP_USER_PROJECT_ID_KEY) + .setValue(AnyValue.newBuilder().setStringValue(DUMMY_GCP_PROJECT)) + .build())); } private static void verifyRequestHeaders(List extractedHeaders) { - assertFalse(extractedHeaders.isEmpty()); + assertThat(extractedHeaders).isNotEmpty(); // verify if extension added the required headers extractedHeaders.forEach( headers -> { - assertTrue(headers.containsEntry(QUOTA_USER_PROJECT_HEADER, DUMMY_GCP_QUOTA_PROJECT)); - assertTrue(headers.containsEntry("Authorization", "Bearer fake.access_token")); + assertThat(headers.containsEntry(QUOTA_USER_PROJECT_HEADER, DUMMY_GCP_QUOTA_PROJECT)) + .isTrue(); + assertThat(headers.containsEntry("Authorization", "Bearer fake.access_token")).isTrue(); }); } diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java index fad784e62..9c5ea516d 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestServiceTest.java @@ -6,7 +6,7 @@ package io.opentelemetry.opamp.client.internal.request.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.doThrow; diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java index becdbc69b..6e96554ea 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/request/service/WebSocketRequestServiceTest.java @@ -6,7 +6,7 @@ package io.opentelemetry.opamp.client.internal.request.service; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.fail; +import static org.assertj.core.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; import static org.mockito.Mockito.inOrder; From f3a54dd0f313360861e58d20558a1875a9dc7b04 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 12 Sep 2025 15:11:07 -0700 Subject: [PATCH 281/371] Apply final to public API classes where possible - ibm-mq-metrics (#2214) --- .../src/main/java/io/opentelemetry/ibm/mq/WmqContext.java | 2 +- .../src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java | 2 +- .../java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java | 2 +- .../main/java/io/opentelemetry/ibm/mq/config/QueueManager.java | 2 +- .../java/io/opentelemetry/ibm/mq/config/ResourceFilters.java | 2 +- .../io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java | 2 +- .../src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java index 6ded88547..b3fafadb6 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqContext.java @@ -17,7 +17,7 @@ * authorization.
* It also validates the arguments passed for various scenarios. */ -public class WmqContext { +public final class WmqContext { private static final String TRANSPORT_TYPE_CLIENT = "Client"; private static final String TRANSPORT_TYPE_BINDINGS = "Bindings"; diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java index 832527fb3..9611604cb 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/WmqMonitor.java @@ -43,7 +43,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class WmqMonitor { +public final class WmqMonitor { private static final Logger logger = LoggerFactory.getLogger(WmqMonitor.class); diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java index f02f05cc0..862edd30a 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ExcludeFilters.java @@ -12,7 +12,7 @@ import java.util.Set; /** A jackson databind class used for config. */ -public class ExcludeFilters { +public final class ExcludeFilters { private String type = "UNKNOWN"; private Set values = new HashSet<>(); diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java index 685840977..11769832f 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/QueueManager.java @@ -10,7 +10,7 @@ /** This is a jackson databind class used purely for config. */ @JsonIgnoreProperties(ignoreUnknown = true) -public class QueueManager { +public final class QueueManager { @Nullable private String host; private int port = -1; diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java index 72a3a9f42..86a30360e 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/config/ResourceFilters.java @@ -8,7 +8,7 @@ import java.util.HashSet; import java.util.Set; -public class ResourceFilters { +public final class ResourceFilters { private Set include = new HashSet<>(); private Set exclude = new HashSet<>(); diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java index 6ed74dad9..688f9541d 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/metricscollector/MessageBuddy.java @@ -12,7 +12,7 @@ import com.ibm.mq.headers.pcf.PCFMessage; import java.time.Instant; -public class MessageBuddy { +public final class MessageBuddy { private MessageBuddy() {} diff --git a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java index cefa9e5d9..cf633b953 100644 --- a/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java +++ b/ibm-mq-metrics/src/main/java/io/opentelemetry/ibm/mq/util/WmqUtil.java @@ -16,7 +16,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class WmqUtil { +public final class WmqUtil { private static final Logger logger = LoggerFactory.getLogger(WmqUtil.class); From 492af6ccf6766662036c481f7d2b94dfade8b87a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 12 Sep 2025 15:11:21 -0700 Subject: [PATCH 282/371] Add japicmp (#2218) --- .github/workflows/build-common.yml | 16 ++ .github/workflows/release.yml | 55 ++++-- buildSrc/build.gradle.kts | 2 + .../otel.japicmp-conventions.gradle.kts | 158 ++++++++++++++++++ .../kotlin/otel.java-conventions.gradle.kts | 1 + .../opentelemetry-aws-xray.txt | 2 + 6 files changed, 219 insertions(+), 15 deletions(-) create mode 100644 buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts create mode 100644 docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index a21ac362a..bb50858cc 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -56,6 +56,22 @@ jobs: - name: Build run: ./gradlew build -x spotlessCheck -x test ${{ inputs.no-build-cache && '--no-build-cache' || '' }} + - name: Check for jApiCmp diffs + # The jApiCmp diff compares current to latest, which isn't appropriate for release branches + if: ${{ !startsWith(github.ref_name, 'release/') && !startsWith(github.base_ref, 'release/') }} + run: | + # need to "git add" in case any generated files did not already exist + git add docs/apidiffs + if git diff --cached --quiet + then + echo "No diff detected." + else + echo "Diff detected - did you run './gradlew jApiCmp'?" + git diff --cached --name-only + git diff --cached + exit 1 + fi + test: name: Test runs-on: ${{ matrix.os }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 24326e7a4..22b40d3b3 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -173,13 +173,15 @@ jobs: echo "version=$VERSION" >> $GITHUB_OUTPUT - merge-change-log-to-main: + post-release-updates: permissions: contents: write # for git push to PR branch runs-on: ubuntu-latest needs: - release steps: + # add change log sync (if any) into this PR since the apidiff update + # is required before any other PR can be merged anyway - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - name: Copy change log section from release branch @@ -201,6 +203,37 @@ jobs: release_date=$(gh release view v$VERSION --json publishedAt --jq .publishedAt | sed 's/T.*//') RELEASE_DATE=$release_date .github/scripts/merge-change-log-after-release.sh + - name: Wait for release to be available in maven central + env: + VERSION: ${{ needs.release.outputs.version }} + run: | + until curl --silent \ + --show-error \ + --output /dev/null \ + --head \ + --fail \ + https://repo1.maven.org/maven2/io/opentelemetry/contrib/opentelemetry-aws-xray/$VERSION/opentelemetry-aws-xray-$VERSION.jar + do + sleep 60 + done + + - name: Set up JDK for running Gradle + uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 + with: + distribution: temurin + java-version: 17 + + - name: Set up Gradle + uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + + - name: Update apidiff baseline + env: + VERSION: ${{ needs.release.outputs.version }} + PRIOR_VERSION: ${{ needs.release.outputs.prior-version }} + run: | + ./gradlew japicmp -PapiBaseVersion=$PRIOR_VERSION -PapiNewVersion=$VERSION + ./gradlew --refresh-dependencies japicmp + - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh @@ -216,22 +249,14 @@ jobs: # not using secrets.GITHUB_TOKEN since pull requests from that token do not run workflows GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} run: | - if git diff --quiet; then - if [[ $VERSION == *.0 ]]; then - echo there are no updates to merge, not creating pull request - exit 0 # success - else - echo patch release notes did not get applied for some reason - exit 1 # failure - fi - fi - - message="Merge change log updates from $GITHUB_REF_NAME" - body="Merge log updates from \`$GITHUB_REF_NAME\`." - branch="otelbot/merge-change-log-updates-from-${GITHUB_REF_NAME//\//-}" + message="Post-release updates for $VERSION" + body="Post-release updates for `$VERSION`." + branch="otelbot/update-apidiff-baseline-to-released-version-${VERSION}" git checkout -b $branch - git commit -a -m "$message" + git add CHANGELOG.md + git add docs/apidiffs + git commit -m "$message" git push --set-upstream origin $branch gh pr create --title "$message" \ --body "$body" \ diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 87a4312f4..03f959388 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -18,6 +18,8 @@ dependencies { implementation("org.owasp:dependency-check-gradle:12.1.3") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") implementation("com.gradle:develocity-gradle-plugin:4.1.1") + implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6") + implementation("com.google.auto.value:auto-value-annotations:1.11.0") } spotless { diff --git a/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts new file mode 100644 index 000000000..3905c2526 --- /dev/null +++ b/buildSrc/src/main/kotlin/otel.japicmp-conventions.gradle.kts @@ -0,0 +1,158 @@ +import com.google.auto.value.AutoValue +import japicmp.model.* +import me.champeau.gradle.japicmp.JapicmpTask +import me.champeau.gradle.japicmp.report.Violation +import me.champeau.gradle.japicmp.report.stdrules.* + + +plugins { + base + + id("me.champeau.gradle.japicmp") +} + +/** + * The latest *released* version of the project. Evaluated lazily so the work is only done if necessary. + */ +val latestReleasedVersion: String by lazy { + // hack to find the current released version of the project + val temp: Configuration = configurations.create("tempConfig") { + resolutionStrategy.cacheChangingModulesFor(0, "seconds") + resolutionStrategy.cacheDynamicVersionsFor(0, "seconds") + } + // pick aws-xray, since it's a stable module that's always there. + dependencies.add(temp.name, "io.opentelemetry.contrib:opentelemetry-aws-xray:latest.release") + val moduleVersion = configurations["tempConfig"].resolvedConfiguration.firstLevelModuleDependencies.elementAt(0).moduleVersion + configurations.remove(temp) + logger.debug("Discovered latest release version: " + moduleVersion) + moduleVersion +} + +class AllowNewAbstractMethodOnAutovalueClasses : AbstractRecordingSeenMembers() { + override fun maybeAddViolation(member: JApiCompatibility): Violation? { + val allowableAutovalueChanges = setOf(JApiCompatibilityChangeType.METHOD_ABSTRACT_ADDED_TO_CLASS, + JApiCompatibilityChangeType.METHOD_ADDED_TO_PUBLIC_CLASS, JApiCompatibilityChangeType.ANNOTATION_ADDED) + if (member.compatibilityChanges.filter { !allowableAutovalueChanges.contains(it.type) }.isEmpty() && + member is JApiMethod && isAutoValueClass(member.getjApiClass())) + { + return Violation.accept(member, "Autovalue will automatically add implementation") + } + if (member.compatibilityChanges.isEmpty() && + member is JApiClass && isAutoValueClass(member)) { + return Violation.accept(member, "Autovalue class modification is allowed") + } + return null + } + + fun isAutoValueClass(japiClass: JApiClass): Boolean { + return japiClass.newClass.get().getAnnotation(AutoValue::class.java) != null || + japiClass.newClass.get().getAnnotation(AutoValue.Builder::class.java) != null + } +} + +class SourceIncompatibleRule : AbstractRecordingSeenMembers() { + override fun maybeAddViolation(member: JApiCompatibility): Violation? { + if (!member.isSourceCompatible()) { + return Violation.error(member, "Not source compatible: $member") + } + return null + } +} + +/** + * Locate the project's artifact of a particular version. + */ +fun findArtifact(version: String): File { + val existingGroup = group + try { + // Temporarily change the group name because we want to fetch an artifact with the same + // Maven coordinates as the project, which Gradle would not allow otherwise. + group = "virtual_group" + val depModule = "io.opentelemetry.contrib:${base.archivesName.get()}:$version@jar" + val depJar = "${base.archivesName.get()}-$version.jar" + val configuration: Configuration = configurations.detachedConfiguration( + dependencies.create(depModule), + ) + return files(configuration.files).filter { + it.name.equals(depJar) + }.singleFile + } finally { + group = existingGroup + } +} + +// generate the api diff report for any module that is stable and publishes a jar. +if (project.findProperty("otel.stable") == "true" && !project.name.startsWith("bom")) { + afterEvaluate { + tasks { + val jApiCmp by registering(JapicmpTask::class) { + dependsOn("jar") + + // the japicmp "new" version is either the user-specified one, or the locally built jar. + val apiNewVersion: String? by project + val newArtifact = apiNewVersion?.let { findArtifact(it) } + ?: file(getByName("jar").archiveFile) + newClasspath.from(files(newArtifact)) + + // only output changes, not everything + onlyModified.set(true) + + // the japicmp "old" version is either the user-specified one, or the latest release. + val apiBaseVersion: String? by project + val baselineVersion = apiBaseVersion ?: latestReleasedVersion + oldClasspath.from( + try { + files(findArtifact(baselineVersion)) + } catch (e: Exception) { + // if we can't find the baseline artifact, this is probably one that's never been published before, + // so publish the whole API. We do that by flipping this flag, and comparing the current against nothing. + onlyModified.set(false) + files() + }, + ) + + // Reproduce defaults from https://github.com/melix/japicmp-gradle-plugin/blob/09f52739ef1fccda6b4310cf3f4b19dc97377024/src/main/java/me/champeau/gradle/japicmp/report/ViolationsGenerator.java#L130 + // with some changes. + val exclusions = mutableListOf() + // Generics are not detected correctly + exclusions.add("CLASS_GENERIC_TEMPLATE_CHANGED") + // Allow new default methods on interfaces + exclusions.add("METHOD_NEW_DEFAULT") + // Allow adding default implementations for default methods + exclusions.add("METHOD_ABSTRACT_NOW_DEFAULT") + // Bug prevents recognizing default methods of superinterface. + // Fixed in https://github.com/siom79/japicmp/pull/343 but not yet available in me.champeau.gradle.japicmp + exclusions.add("METHOD_ABSTRACT_ADDED_IN_IMPLEMENTED_INTERFACE") + compatibilityChangeExcludes.set(exclusions) + richReport { + addSetupRule(RecordSeenMembersSetup::class.java) + addRule(JApiChangeStatus.NEW, SourceCompatibleRule::class.java) + addRule(JApiChangeStatus.MODIFIED, SourceCompatibleRule::class.java) + addRule(JApiChangeStatus.UNCHANGED, UnchangedMemberRule::class.java) + // Allow new abstract methods on autovalue + addRule(AllowNewAbstractMethodOnAutovalueClasses::class.java) + addRule(BinaryIncompatibleRule::class.java) + // Disallow source incompatible changes, which are allowed by default for some reason + addRule(SourceIncompatibleRule::class.java) + } + + // this is needed so that we only consider the current artifact, and not dependencies + ignoreMissingClasses.set(true) + packageExcludes.addAll( + "*.internal", + "*.internal.*" + ) + annotationExcludes.add("@kotlin.Metadata") + val baseVersionString = if (apiBaseVersion == null) "latest" else baselineVersion + txtOutputFile.set( + apiNewVersion?.let { file("$rootDir/docs/apidiffs/${apiNewVersion}_vs_$baselineVersion/${base.archivesName.get()}.txt") } + ?: file("$rootDir/docs/apidiffs/current_vs_$baseVersionString/${base.archivesName.get()}.txt"), + ) + } + // have the check task depend on the api comparison task, to make it more likely it will get used. + named("check") { + dependsOn(jApiCmp) + } + } + } +} diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 894e8bda3..48f443d67 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -7,6 +7,7 @@ plugins { id("otel.errorprone-conventions") id("otel.spotless-conventions") + id("otel.japicmp-conventions") id("org.owasp.dependencycheck") } diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt b/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt new file mode 100644 index 000000000..fb005385c --- /dev/null +++ b/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt @@ -0,0 +1,2 @@ +Comparing source compatibility of opentelemetry-aws-xray-1.50.0-SNAPSHOT.jar against opentelemetry-aws-xray-1.49.0.jar +No changes. \ No newline at end of file From 1a07bb8e0491cf9f31a7ba47233dd7da5ecc9bb6 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 12 Sep 2025 15:12:14 -0700 Subject: [PATCH 283/371] Use assertj consistently everywhere (#2253) --- ...ConsistentProbabilityBasedSamplerTest.java | 5 +- ...entReservoirSamplingSpanProcessorTest.java | 5 +- .../consistent/ConsistentSamplerTest.java | 65 ++++++------ .../consistent/OtelTraceStateTest.java | 99 +++++++++-------- .../consistent56/OtelTraceStateTest.java | 100 ++++++++++-------- 5 files changed, 140 insertions(+), 134 deletions(-) diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java index d8cbe3a16..a05506bc8 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentProbabilityBasedSamplerTest.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.sampler.consistent; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertTrue; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.SpanKind; @@ -63,8 +62,8 @@ private void test(SplittableRandom rng, double samplingProbability) { .getUpdatedTraceState(TraceState.getDefault()) .get(OtelTraceState.TRACE_STATE_KEY); OtelTraceState traceState = OtelTraceState.parse(traceStateString); - assertTrue(traceState.hasValidR()); - assertTrue(traceState.hasValidP()); + assertThat(traceState.hasValidR()).isTrue(); + assertThat(traceState.hasValidP()).isTrue(); observedPvalues.merge(traceState.getP(), 1L, Long::sum); } } diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java index 5eaefd482..1415d0f6f 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentReservoirSamplingSpanProcessorTest.java @@ -11,7 +11,6 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatCode; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.awaitility.Awaitility.await; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.argThat; @@ -566,8 +565,8 @@ private void testConsistentSampling( String traceStateString = spanData.getSpanContext().getTraceState().get(OtelTraceState.TRACE_STATE_KEY); OtelTraceState traceState = OtelTraceState.parse(traceStateString); - assertTrue(traceState.hasValidR()); - assertTrue(traceState.hasValidP()); + assertThat(traceState.hasValidR()).isTrue(); + assertThat(traceState.hasValidP()).isTrue(); observedPvalues.merge(traceState.getP(), 1L, Long::sum); totalAdjustedCount += 1L << traceState.getP(); spanNameCounts.merge(spanData.getName(), 1L, Long::sum); diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentSamplerTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentSamplerTest.java index 082ac3068..1a61868c8 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentSamplerTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/ConsistentSamplerTest.java @@ -8,9 +8,7 @@ import static io.opentelemetry.contrib.sampler.consistent.OtelTraceState.getInvalidP; import static io.opentelemetry.contrib.sampler.consistent.OtelTraceState.getInvalidR; import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -32,45 +30,50 @@ class ConsistentSamplerTest { @Test void testGetSamplingRate() { - assertThrows( - IllegalArgumentException.class, () -> ConsistentSampler.getSamplingProbability(-1)); + assertThatThrownBy(() -> ConsistentSampler.getSamplingProbability(-1)) + .isInstanceOf(IllegalArgumentException.class); for (int i = 0; i < OtelTraceState.getMaxP() - 1; i += 1) { - assertEquals(Math.pow(0.5, i), ConsistentSampler.getSamplingProbability(i)); + assertThat(ConsistentSampler.getSamplingProbability(i)).isEqualTo(Math.pow(0.5, i)); } - assertEquals(0., ConsistentSampler.getSamplingProbability(OtelTraceState.getMaxP())); - assertThrows( - IllegalArgumentException.class, - () -> ConsistentSampler.getSamplingProbability(OtelTraceState.getMaxP() + 1)); + assertThat(ConsistentSampler.getSamplingProbability(OtelTraceState.getMaxP())).isEqualTo(0.); + assertThatThrownBy(() -> ConsistentSampler.getSamplingProbability(OtelTraceState.getMaxP() + 1)) + .isInstanceOf(IllegalArgumentException.class); } @Test void testGetLowerBoundP() { - assertEquals(0, ConsistentSampler.getLowerBoundP(1.0)); - assertEquals(0, ConsistentSampler.getLowerBoundP(Math.nextDown(1.0))); + assertThat(ConsistentSampler.getLowerBoundP(1.0)).isEqualTo(0); + assertThat(ConsistentSampler.getLowerBoundP(Math.nextDown(1.0))).isEqualTo(0); for (int i = 1; i < OtelTraceState.getMaxP() - 1; i += 1) { double samplingProbability = Math.pow(0.5, i); - assertEquals(i, ConsistentSampler.getLowerBoundP(samplingProbability)); - assertEquals(i - 1, ConsistentSampler.getLowerBoundP(Math.nextUp(samplingProbability))); - assertEquals(i, ConsistentSampler.getLowerBoundP(Math.nextDown(samplingProbability))); + assertThat(ConsistentSampler.getLowerBoundP(samplingProbability)).isEqualTo(i); + assertThat(ConsistentSampler.getLowerBoundP(Math.nextUp(samplingProbability))) + .isEqualTo(i - 1); + assertThat(ConsistentSampler.getLowerBoundP(Math.nextDown(samplingProbability))).isEqualTo(i); } - assertEquals(OtelTraceState.getMaxP() - 1, ConsistentSampler.getLowerBoundP(Double.MIN_NORMAL)); - assertEquals(OtelTraceState.getMaxP() - 1, ConsistentSampler.getLowerBoundP(Double.MIN_VALUE)); - assertEquals(OtelTraceState.getMaxP(), ConsistentSampler.getLowerBoundP(0.0)); + assertThat(ConsistentSampler.getLowerBoundP(Double.MIN_NORMAL)) + .isEqualTo(OtelTraceState.getMaxP() - 1); + assertThat(ConsistentSampler.getLowerBoundP(Double.MIN_VALUE)) + .isEqualTo(OtelTraceState.getMaxP() - 1); + assertThat(ConsistentSampler.getLowerBoundP(0.0)).isEqualTo(OtelTraceState.getMaxP()); } @Test void testGetUpperBoundP() { - assertEquals(0, ConsistentSampler.getUpperBoundP(1.0)); - assertEquals(1, ConsistentSampler.getUpperBoundP(Math.nextDown(1.0))); + assertThat(ConsistentSampler.getUpperBoundP(1.0)).isEqualTo(0); + assertThat(ConsistentSampler.getUpperBoundP(Math.nextDown(1.0))).isEqualTo(1); for (int i = 1; i < OtelTraceState.getMaxP() - 1; i += 1) { double samplingProbability = Math.pow(0.5, i); - assertEquals(i, ConsistentSampler.getUpperBoundP(samplingProbability)); - assertEquals(i, ConsistentSampler.getUpperBoundP(Math.nextUp(samplingProbability))); - assertEquals(i + 1, ConsistentSampler.getUpperBoundP(Math.nextDown(samplingProbability))); + assertThat(ConsistentSampler.getUpperBoundP(samplingProbability)).isEqualTo(i); + assertThat(ConsistentSampler.getUpperBoundP(Math.nextUp(samplingProbability))).isEqualTo(i); + assertThat(ConsistentSampler.getUpperBoundP(Math.nextDown(samplingProbability))) + .isEqualTo(i + 1); } - assertEquals(OtelTraceState.getMaxP(), ConsistentSampler.getUpperBoundP(Double.MIN_NORMAL)); - assertEquals(OtelTraceState.getMaxP(), ConsistentSampler.getUpperBoundP(Double.MIN_VALUE)); - assertEquals(OtelTraceState.getMaxP(), ConsistentSampler.getUpperBoundP(0.0)); + assertThat(ConsistentSampler.getUpperBoundP(Double.MIN_NORMAL)) + .isEqualTo(OtelTraceState.getMaxP()); + assertThat(ConsistentSampler.getUpperBoundP(Double.MIN_VALUE)) + .isEqualTo(OtelTraceState.getMaxP()); + assertThat(ConsistentSampler.getUpperBoundP(0.0)).isEqualTo(OtelTraceState.getMaxP()); } @Test @@ -168,18 +171,18 @@ private static void assertConsistentSampling( SamplingResult samplingResult = sampler.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks); - assertEquals(expectSampled, getSampledFlag(samplingResult)); + assertThat(getSampledFlag(samplingResult)).isEqualTo(expectSampled); OptionalInt p = getP(samplingResult, parentContext); if (OtelTraceState.isValidP(expectedP)) { - assertEquals(expectedP, p.getAsInt()); + assertThat(p.getAsInt()).isEqualTo(expectedP); } else { - assertFalse(p.isPresent()); + assertThat(p.isPresent()).isFalse(); } OptionalInt r = getR(samplingResult, parentContext); if (OtelTraceState.isValidR(expectedR)) { - assertEquals(expectedR, r.getAsInt()); + assertThat(r.getAsInt()).isEqualTo(expectedR); } else { - assertFalse(r.isPresent()); + assertThat(r.isPresent()).isFalse(); } } diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java index 39bbf5dd9..fbb6b6dc7 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent/OtelTraceStateTest.java @@ -5,11 +5,10 @@ package io.opentelemetry.contrib.sampler.consistent; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; class OtelTraceStateTest { @@ -21,59 +20,57 @@ private static String getXString(int len) { @Test void test() { - Assertions.assertEquals("", OtelTraceState.parse("").serialize()); - assertEquals("", OtelTraceState.parse("").serialize()); + assertThat(OtelTraceState.parse("").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("").serialize()).isEqualTo(""); - assertEquals("", OtelTraceState.parse("a").serialize()); - assertEquals("", OtelTraceState.parse("#").serialize()); - assertEquals("", OtelTraceState.parse(" ").serialize()); + assertThat(OtelTraceState.parse("a").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("#").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse(" ").serialize()).isEqualTo(""); - assertEquals("p:5", OtelTraceState.parse("p:5").serialize()); - assertEquals("p:63", OtelTraceState.parse("p:63").serialize()); - assertEquals("", OtelTraceState.parse("p:64").serialize()); - assertEquals("", OtelTraceState.parse("p:5;").serialize()); - assertEquals("", OtelTraceState.parse("p:99").serialize()); - assertEquals("", OtelTraceState.parse("p:").serialize()); - assertEquals("", OtelTraceState.parse("p:232").serialize()); - assertEquals("", OtelTraceState.parse("x;p:5").serialize()); - assertEquals("", OtelTraceState.parse("p:5;x").serialize()); - assertEquals("p:5;x:3", OtelTraceState.parse("x:3;p:5").serialize()); - assertEquals("p:5;x:3", OtelTraceState.parse("p:5;x:3").serialize()); - assertEquals("", OtelTraceState.parse("p:5;x:3;").serialize()); - assertEquals( - "p:5;a:" + getXString(246) + ";x:3", - OtelTraceState.parse("a:" + getXString(246) + ";p:5;x:3").serialize()); - assertEquals("", OtelTraceState.parse("a:" + getXString(247) + ";p:5;x:3").serialize()); + assertThat(OtelTraceState.parse("p:5").serialize()).isEqualTo("p:5"); + assertThat(OtelTraceState.parse("p:63").serialize()).isEqualTo("p:63"); + assertThat(OtelTraceState.parse("p:64").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("p:5;").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("p:99").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("p:").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("p:232").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("x;p:5").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("p:5;x").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("x:3;p:5").serialize()).isEqualTo("p:5;x:3"); + assertThat(OtelTraceState.parse("p:5;x:3").serialize()).isEqualTo("p:5;x:3"); + assertThat(OtelTraceState.parse("p:5;x:3;").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("a:" + getXString(246) + ";p:5;x:3").serialize()) + .isEqualTo("p:5;a:" + getXString(246) + ";x:3"); + assertThat(OtelTraceState.parse("a:" + getXString(247) + ";p:5;x:3").serialize()).isEqualTo(""); - assertEquals("r:5", OtelTraceState.parse("r:5").serialize()); - assertEquals("r:62", OtelTraceState.parse("r:62").serialize()); - assertEquals("", OtelTraceState.parse("r:63").serialize()); - assertEquals("", OtelTraceState.parse("r:5;").serialize()); - assertEquals("", OtelTraceState.parse("r:99").serialize()); - assertEquals("", OtelTraceState.parse("r:").serialize()); - assertEquals("", OtelTraceState.parse("r:232").serialize()); - assertEquals("", OtelTraceState.parse("x;r:5").serialize()); - assertEquals("", OtelTraceState.parse("r:5;x").serialize()); - assertEquals("r:5;x:3", OtelTraceState.parse("x:3;r:5").serialize()); - assertEquals("r:5;x:3", OtelTraceState.parse("r:5;x:3").serialize()); - assertEquals("", OtelTraceState.parse("r:5;x:3;").serialize()); - assertEquals( - "r:5;a:" + getXString(246) + ";x:3", - OtelTraceState.parse("a:" + getXString(246) + ";r:5;x:3").serialize()); - assertEquals("", OtelTraceState.parse("a:" + getXString(247) + ";r:5;x:3").serialize()); + assertThat(OtelTraceState.parse("r:5").serialize()).isEqualTo("r:5"); + assertThat(OtelTraceState.parse("r:62").serialize()).isEqualTo("r:62"); + assertThat(OtelTraceState.parse("r:63").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("r:5;").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("r:99").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("r:").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("r:232").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("x;r:5").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("r:5;x").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("x:3;r:5").serialize()).isEqualTo("r:5;x:3"); + assertThat(OtelTraceState.parse("r:5;x:3").serialize()).isEqualTo("r:5;x:3"); + assertThat(OtelTraceState.parse("r:5;x:3;").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("a:" + getXString(246) + ";r:5;x:3").serialize()) + .isEqualTo("r:5;a:" + getXString(246) + ";x:3"); + assertThat(OtelTraceState.parse("a:" + getXString(247) + ";r:5;x:3").serialize()).isEqualTo(""); - assertEquals("p:7;r:5", OtelTraceState.parse("r:5;p:7").serialize()); - assertEquals("p:4;r:5", OtelTraceState.parse("r:5;p:4").serialize()); - assertEquals("p:7;r:5", OtelTraceState.parse("r:5;p:7").serialize()); - assertEquals("p:4;r:5", OtelTraceState.parse("r:5;p:4").serialize()); + assertThat(OtelTraceState.parse("r:5;p:7").serialize()).isEqualTo("p:7;r:5"); + assertThat(OtelTraceState.parse("r:5;p:4").serialize()).isEqualTo("p:4;r:5"); + assertThat(OtelTraceState.parse("r:5;p:7").serialize()).isEqualTo("p:7;r:5"); + assertThat(OtelTraceState.parse("r:5;p:4").serialize()).isEqualTo("p:4;r:5"); - assertEquals("r:6", OtelTraceState.parse("r:5;r:6").serialize()); - assertEquals("p:6;r:10", OtelTraceState.parse("p:5;p:6;r:10").serialize()); - assertEquals("", OtelTraceState.parse("p5;p:6;r:10").serialize()); - assertEquals("p:6;r:10;p5:3", OtelTraceState.parse("p5:3;p:6;r:10").serialize()); - assertEquals("", OtelTraceState.parse(":p:6;r:10").serialize()); - assertEquals("", OtelTraceState.parse(";p:6;r:10").serialize()); - assertEquals("", OtelTraceState.parse("_;p:6;r:10").serialize()); - assertEquals("", OtelTraceState.parse("5;p:6;r:10").serialize()); + assertThat(OtelTraceState.parse("r:5;r:6").serialize()).isEqualTo("r:6"); + assertThat(OtelTraceState.parse("p:5;p:6;r:10").serialize()).isEqualTo("p:6;r:10"); + assertThat(OtelTraceState.parse("p5;p:6;r:10").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("p5:3;p:6;r:10").serialize()).isEqualTo("p:6;r:10;p5:3"); + assertThat(OtelTraceState.parse(":p:6;r:10").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse(";p:6;r:10").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("_;p:6;r:10").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("5;p:6;r:10").serialize()).isEqualTo(""); } } diff --git a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java index 7d306d959..8e8b3ef96 100644 --- a/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java +++ b/consistent-sampling/src/test/java/io/opentelemetry/contrib/sampler/consistent56/OtelTraceStateTest.java @@ -5,7 +5,7 @@ package io.opentelemetry.contrib.sampler.consistent56; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -20,56 +20,64 @@ private static String getXString(int len) { @Test void test() { - assertEquals("", OtelTraceState.parse("").serialize()); - assertEquals("", OtelTraceState.parse("").serialize()); + assertThat(OtelTraceState.parse("").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("").serialize()).isEqualTo(""); - assertEquals("", OtelTraceState.parse("a").serialize()); - assertEquals("", OtelTraceState.parse("#").serialize()); - assertEquals("", OtelTraceState.parse(" ").serialize()); + assertThat(OtelTraceState.parse("a").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("#").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse(" ").serialize()).isEqualTo(""); - assertEquals("rv:1234567890abcd", OtelTraceState.parse("rv:1234567890abcd").serialize()); - assertEquals("rv:01020304050607", OtelTraceState.parse("rv:01020304050607").serialize()); - assertEquals("", OtelTraceState.parse("rv:1234567890abcde").serialize()); + assertThat(OtelTraceState.parse("rv:1234567890abcd").serialize()) + .isEqualTo("rv:1234567890abcd"); + assertThat(OtelTraceState.parse("rv:01020304050607").serialize()) + .isEqualTo("rv:01020304050607"); + assertThat(OtelTraceState.parse("rv:1234567890abcde").serialize()).isEqualTo(""); - assertEquals("th:1234567890abcd", OtelTraceState.parse("th:1234567890abcd").serialize()); - assertEquals("th:01020304050607", OtelTraceState.parse("th:01020304050607").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:10000000000000").serialize()); - assertEquals("th:12345", OtelTraceState.parse("th:1234500000000").serialize()); - assertEquals("th:0", OtelTraceState.parse("th:0").serialize()); // TODO - assertEquals("", OtelTraceState.parse("th:100000000000000").serialize()); - assertEquals("", OtelTraceState.parse("th:1234567890abcde").serialize()); + assertThat(OtelTraceState.parse("th:1234567890abcd").serialize()) + .isEqualTo("th:1234567890abcd"); + assertThat(OtelTraceState.parse("th:01020304050607").serialize()) + .isEqualTo("th:01020304050607"); + assertThat(OtelTraceState.parse("th:10000000000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:1234500000000").serialize()).isEqualTo("th:12345"); + assertThat(OtelTraceState.parse("th:0").serialize()).isEqualTo("th:0"); // TODO + assertThat(OtelTraceState.parse("th:100000000000000").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("th:1234567890abcde").serialize()).isEqualTo(""); - assertEquals( - "th:1234567890abcd;rv:1234567890abcd;a:" + getXString(214) + ";x:3", - OtelTraceState.parse("a:" + getXString(214) + ";rv:1234567890abcd;th:1234567890abcd;x:3") - .serialize()); - assertEquals( - "", - OtelTraceState.parse("a:" + getXString(215) + ";rv:1234567890abcd;th:1234567890abcd;x:3") - .serialize()); + assertThat( + OtelTraceState.parse( + "a:" + getXString(214) + ";rv:1234567890abcd;th:1234567890abcd;x:3") + .serialize()) + .isEqualTo("th:1234567890abcd;rv:1234567890abcd;a:" + getXString(214) + ";x:3"); + assertThat( + OtelTraceState.parse( + "a:" + getXString(215) + ";rv:1234567890abcd;th:1234567890abcd;x:3") + .serialize()) + .isEqualTo(""); - assertEquals("", OtelTraceState.parse("th:x").serialize()); - assertEquals("", OtelTraceState.parse("th:100000000000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:10000000000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:1000000000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:100000000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:10000000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:1000000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:100000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:10000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:1000000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:100000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:10000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:1000").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:100").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:10").serialize()); - assertEquals("th:1", OtelTraceState.parse("th:1").serialize()); + assertThat(OtelTraceState.parse("th:x").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("th:100000000000000").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("th:10000000000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:1000000000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:100000000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:10000000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:1000000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:100000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:10000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:1000000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:100000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:10000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:1000").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:100").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:10").serialize()).isEqualTo("th:1"); + assertThat(OtelTraceState.parse("th:1").serialize()).isEqualTo("th:1"); - assertEquals("th:10000000000001", OtelTraceState.parse("th:10000000000001").serialize()); - assertEquals("th:1000000000001", OtelTraceState.parse("th:10000000000010").serialize()); - assertEquals("", OtelTraceState.parse("rv:x").serialize()); - assertEquals("", OtelTraceState.parse("rv:100000000000000").serialize()); - assertEquals("rv:10000000000000", OtelTraceState.parse("rv:10000000000000").serialize()); - assertEquals("", OtelTraceState.parse("rv:1000000000000").serialize()); + assertThat(OtelTraceState.parse("th:10000000000001").serialize()) + .isEqualTo("th:10000000000001"); + assertThat(OtelTraceState.parse("th:10000000000010").serialize()).isEqualTo("th:1000000000001"); + assertThat(OtelTraceState.parse("rv:x").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("rv:100000000000000").serialize()).isEqualTo(""); + assertThat(OtelTraceState.parse("rv:10000000000000").serialize()) + .isEqualTo("rv:10000000000000"); + assertThat(OtelTraceState.parse("rv:1000000000000").serialize()).isEqualTo(""); } } From fcf53fe4e5efe7f6b2fc8394b3ad14cfed52da26 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Sun, 14 Sep 2025 11:05:59 -0700 Subject: [PATCH 284/371] Pin npm hash (#2255) --- .github/renovate.json5 | 10 --------- .github/scripts/package-lock.json | 27 +++++++++++++++++++++++ .github/scripts/package.json | 9 ++++++++ .github/workflows/assign-issue-owners.yml | 4 ++-- 4 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 .github/scripts/package-lock.json create mode 100644 .github/scripts/package.json diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 6a0416b69..c6a64fbe0 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -198,16 +198,6 @@ 'npx (?[^@]+)@(?[^\\s]+)', ], }, - { - customType: 'regex', - datasourceTemplate: 'npm', - managerFilePatterns: [ - '.github/workflows/**', - ], - matchStrings: [ - 'npm install (?[^@\\s]+)@(?[^\\s]+)', - ], - }, { customType: 'regex', datasourceTemplate: 'java-version', diff --git a/.github/scripts/package-lock.json b/.github/scripts/package-lock.json new file mode 100644 index 000000000..513b765ca --- /dev/null +++ b/.github/scripts/package-lock.json @@ -0,0 +1,27 @@ +{ + "name": "github-scripts", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-scripts", + "version": "1.0.0", + "dependencies": { + "yaml": "2.8.0" + } + }, + "node_modules/yaml": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", + "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + } + } + } +} diff --git a/.github/scripts/package.json b/.github/scripts/package.json new file mode 100644 index 000000000..7477003a2 --- /dev/null +++ b/.github/scripts/package.json @@ -0,0 +1,9 @@ +{ + "//": "Dependencies for GitHub Actions workflows that use actions/github-script", + "name": "github-scripts", + "version": "1.0.0", + "private": true, + "dependencies": { + "yaml": "2.8.0" + } +} diff --git a/.github/workflows/assign-issue-owners.yml b/.github/workflows/assign-issue-owners.yml index 11965eae5..7d7acba67 100644 --- a/.github/workflows/assign-issue-owners.yml +++ b/.github/workflows/assign-issue-owners.yml @@ -19,8 +19,8 @@ jobs: - name: Checkout repository uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - name: Install yaml - run: npm install yaml@2.8.1 + - name: Install yaml dependency used below + run: npm install .github/scripts - name: Parse component label and assign owners uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 From 78c6d0b9991edf38f7db9a995c271e5195d08c6b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 14 Sep 2025 12:57:09 -0700 Subject: [PATCH 285/371] fix(deps): update all patch versions (#2258) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/scripts/package-lock.json | 8 ++++---- .github/scripts/package.json | 2 +- .github/workflows/auto-spotless-apply.yml | 2 +- .github/workflows/backport.yml | 2 +- .github/workflows/ossf-scorecard.yml | 2 +- .github/workflows/prepare-patch-release.yml | 2 +- .github/workflows/prepare-release-branch.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/scripts/package-lock.json b/.github/scripts/package-lock.json index 513b765ca..f364e1106 100644 --- a/.github/scripts/package-lock.json +++ b/.github/scripts/package-lock.json @@ -8,13 +8,13 @@ "name": "github-scripts", "version": "1.0.0", "dependencies": { - "yaml": "2.8.0" + "yaml": "2.8.1" } }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "license": "ISC", "bin": { "yaml": "bin.mjs" diff --git a/.github/scripts/package.json b/.github/scripts/package.json index 7477003a2..efca3b345 100644 --- a/.github/scripts/package.json +++ b/.github/scripts/package.json @@ -4,6 +4,6 @@ "version": "1.0.0", "private": true, "dependencies": { - "yaml": "2.8.0" + "yaml": "2.8.1" } } diff --git a/.github/workflows/auto-spotless-apply.yml b/.github/workflows/auto-spotless-apply.yml index 36da59e0d..b363b4977 100644 --- a/.github/workflows/auto-spotless-apply.yml +++ b/.github/workflows/auto-spotless-apply.yml @@ -32,7 +32,7 @@ jobs: echo "exists=true" >> $GITHUB_OUTPUT fi - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 if: steps.unzip-patch.outputs.exists == 'true' id: otelbot-token with: diff --git a/.github/workflows/backport.yml b/.github/workflows/backport.yml index 582a82f3f..a798378c6 100644 --- a/.github/workflows/backport.yml +++ b/.github/workflows/backport.yml @@ -29,7 +29,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index e939d15ee..c186d16e2 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -23,7 +23,7 @@ jobs: with: persist-credentials: false - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: create-token with: # analyzing classic branch protections requires a token with admin read permissions diff --git a/.github/workflows/prepare-patch-release.yml b/.github/workflows/prepare-patch-release.yml index a6d2a64aa..70bfd208a 100644 --- a/.github/workflows/prepare-patch-release.yml +++ b/.github/workflows/prepare-patch-release.yml @@ -47,7 +47,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/prepare-release-branch.yml b/.github/workflows/prepare-release-branch.yml index f1c5160d5..58c5dc8bb 100644 --- a/.github/workflows/prepare-release-branch.yml +++ b/.github/workflows/prepare-release-branch.yml @@ -59,7 +59,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} @@ -116,7 +116,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 22b40d3b3..68800fa37 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -224,7 +224,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@017a9effdb900e5b5b2fddfb590a105619dca3c3 # v4.4.2 + uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Update apidiff baseline env: @@ -237,7 +237,7 @@ jobs: - name: Use CLA approved bot run: .github/scripts/use-cla-approved-bot.sh - - uses: actions/create-github-app-token@a8d616148505b5069dccd32f177bb87d7f39123b # v2.1.1 + - uses: actions/create-github-app-token@67018539274d69449ef7c02e8e71183d1719ab42 # v2.1.4 id: otelbot-token with: app-id: ${{ vars.OTELBOT_APP_ID }} From 83a195d8f4a57bf05f8b1f8a083b7a589dc0369b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 08:25:00 +0300 Subject: [PATCH 286/371] fix(deps): update hipparchus packages to v4.0.2 (#2259) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- consistent-sampling/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/consistent-sampling/build.gradle.kts b/consistent-sampling/build.gradle.kts index 66fca90c9..5fc4135bb 100644 --- a/consistent-sampling/build.gradle.kts +++ b/consistent-sampling/build.gradle.kts @@ -9,8 +9,8 @@ otelJava.moduleName.set("io.opentelemetry.contrib.sampler") dependencies { api("io.opentelemetry:opentelemetry-sdk-trace") api("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") - testImplementation("org.hipparchus:hipparchus-core:4.0.1") - testImplementation("org.hipparchus:hipparchus-stat:4.0.1") + testImplementation("org.hipparchus:hipparchus-core:4.0.2") + testImplementation("org.hipparchus:hipparchus-stat:4.0.2") } tasks { From 269ef392327f9cfd1e76390287339c62828cebc8 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Mon, 15 Sep 2025 13:53:49 +0300 Subject: [PATCH 287/371] Remove commented out dependency (#2264) --- azure-resources/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/azure-resources/build.gradle.kts b/azure-resources/build.gradle.kts index 43a154970..015d0511d 100644 --- a/azure-resources/build.gradle.kts +++ b/azure-resources/build.gradle.kts @@ -30,7 +30,6 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") -// testImplementation("org.mockito:mockito-core") testImplementation("com.google.guava:guava") testImplementation("org.junit.jupiter:junit-jupiter-api") From e010e8df69e372d4a1abe1c43d860a9db051c8a0 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 15 Sep 2025 16:40:37 +0200 Subject: [PATCH 288/371] fix version resolution for semconv (#2263) --- .../contrib/azure/resource/IncubatingAttributes.java | 8 ++++---- .../resource/AzureAppServiceResourceProviderTest.java | 2 +- .../resource/AzureFunctionsResourceProviderTest.java | 2 +- .../src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- dependencyManagement/build.gradle.kts | 10 +++++----- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/IncubatingAttributes.java b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/IncubatingAttributes.java index 491cb99a8..524ca8727 100644 --- a/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/IncubatingAttributes.java +++ b/azure-resources/src/main/java/io/opentelemetry/contrib/azure/resource/IncubatingAttributes.java @@ -24,10 +24,10 @@ final class IncubatingAttributes { AttributeKey.stringKey("cloud.resource_id"); public static final class CloudPlatformIncubatingValues { - public static final String AZURE_VM = "azure_vm"; - public static final String AZURE_AKS = "azure_aks"; - public static final String AZURE_FUNCTIONS = "azure_functions"; - public static final String AZURE_APP_SERVICE = "azure_app_service"; + public static final String AZURE_VM = "azure.vm"; + public static final String AZURE_AKS = "azure.aks"; + public static final String AZURE_FUNCTIONS = "azure.functions"; + public static final String AZURE_APP_SERVICE = "azure.app_service"; private CloudPlatformIncubatingValues() {} } diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java index 75360f0e9..90a7d27b5 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureAppServiceResourceProviderTest.java @@ -48,7 +48,7 @@ void defaultValues() { createResource(DEFAULT_ENV_VARS) .containsEntry(SERVICE_NAME, TEST_WEBSITE_SITE_NAME) .containsEntry(CLOUD_PROVIDER, "azure") - .containsEntry(CLOUD_PLATFORM, "azure_app_service") + .containsEntry(CLOUD_PLATFORM, "azure.app_service") .containsEntry( CLOUD_RESOURCE_ID, "/subscriptions/TEST_WEBSITE_OWNER_NAME/resourceGroups/TEST_WEBSITE_RESOURCE_GROUP/providers/Microsoft.Web/sites/TEST_WEBSITE_SITE_NAME") diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java index e2c39cc7a..b680088e8 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/AzureFunctionsResourceProviderTest.java @@ -38,7 +38,7 @@ class AzureFunctionsResourceProviderTest { void defaultValues() { createResource(DEFAULT_ENV_VARS) .containsEntry(CLOUD_PROVIDER, "azure") - .containsEntry(CLOUD_PLATFORM, "azure_functions") + .containsEntry(CLOUD_PLATFORM, "azure.functions") .containsEntry(FAAS_NAME, TEST_WEBSITE_SITE_NAME) .containsEntry(FAAS_VERSION, TEST_FUNCTION_VERSION) .containsEntry(FAAS_INSTANCE, TEST_WEBSITE_INSTANCE_ID) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 48f443d67..f72c44034 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -77,7 +77,7 @@ tasks { exceptionFormat = TestExceptionFormat.FULL showStandardStreams = true } - + configure { // only care about code coverage for code in this repository // (in particular avoiding netty classes which sometimes end up diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 637f8fbac..0f55bae81 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -14,14 +14,14 @@ dependencies { // under JvmTestSuite so they don't show up as runtime dependencies in license and vulnerability scans // (the constraints section below doesn't have this issue, and will only show up // as runtime dependencies if they are actually used as runtime dependencies) - api(enforcedPlatform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) - api(enforcedPlatform("com.fasterxml.jackson:jackson-bom:2.20.0")) - api(enforcedPlatform("com.google.protobuf:protobuf-bom:4.32.1")) - api(enforcedPlatform("com.squareup.okhttp3:okhttp-bom:5.1.0")) + api(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) + api(platform("com.fasterxml.jackson:jackson-bom:2.20.0")) + api(platform("com.google.protobuf:protobuf-bom:4.32.1")) + api(platform("com.squareup.okhttp3:okhttp-bom:5.1.0")) constraints { api("io.opentelemetry.semconv:opentelemetry-semconv:${semconvVersion}") - api("io.opentelemetry.semconv:opentelemetry-semconv-incubating:${semconvVersion}") + api("io.opentelemetry.semconv:opentelemetry-semconv-incubating:${semconvVersion}-alpha") api("com.google.auto.service:auto-service:1.1.1") api("com.google.auto.service:auto-service-annotations:1.1.1") From c1559991b1c6ce0e1981c2000ef3a7b56a1c3a3f Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 15 Sep 2025 17:46:24 +0200 Subject: [PATCH 289/371] Update otel (#2261) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 0f55bae81..89b286c14 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.19.0-alpha" +val otelInstrumentationVersion = "2.20.0-alpha" val semconvVersion = "1.37.0" javaPlatform { From 9a810892bf695ec485218b1010b1811c83dc7472 Mon Sep 17 00:00:00 2001 From: Lauri Tulmin Date: Tue, 16 Sep 2025 17:27:24 +0300 Subject: [PATCH 290/371] Fix typo (#2265) --- .../internal/RuleBasedRoutingSamplerComponentProvider.java | 4 ++-- .../RuleBasedRoutingSamplerComponentProviderTest.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/samplers/src/main/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProvider.java b/samplers/src/main/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProvider.java index 9e7fb4971..b81cdb546 100644 --- a/samplers/src/main/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProvider.java +++ b/samplers/src/main/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProvider.java @@ -49,7 +49,7 @@ public Sampler create(DeclarativeConfigProperties config) { fallbackSampler = DeclarativeConfiguration.createSampler(fallbackModel); } catch (DeclarativeConfigException e) { throw new DeclarativeConfigException( - "rule_Based_routing sampler failed to create .fallback sampler", e); + "rule_based_routing sampler failed to create .fallback sampler", e); } String spanKindString = config.getString("span_kind", "SERVER"); @@ -92,7 +92,7 @@ public Sampler create(DeclarativeConfigProperties config) { builder.drop(attributeKey, pattern); } else { throw new DeclarativeConfigException( - "rule_based_routing sampler .rules[].action is must be " + "rule_based_routing sampler .rules[].action must be " + ACTION_RECORD_AND_SAMPLE + " or " + ACTION_DROP); diff --git a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java b/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java index c39603352..31767262b 100644 --- a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java +++ b/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java @@ -173,7 +173,7 @@ static Stream createInvalidArgs() { + "rules:\n" + " - attribute: url.path\n" + " pattern: path\n", - "rule_Based_routing sampler failed to create .fallback sampler"), + "rule_based_routing sampler failed to create .fallback sampler"), Arguments.of( "fallback_sampler:\n" + " always_on:\n" @@ -218,6 +218,6 @@ static Stream createInvalidArgs() { + " - attribute: url.path\n" + " pattern: path\n" + " action: foo\n", - "rule_based_routing sampler .rules[].action is must be RECORD_AND_SAMPLE or DROP")); + "rule_based_routing sampler .rules[].action must be RECORD_AND_SAMPLE or DROP")); } } From a2742aaf4c325ffdb88d7378a3a200481fb6bdff Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Tue, 16 Sep 2025 10:58:00 -0700 Subject: [PATCH 291/371] [ibm-mq] Fix links in readme (#2266) --- .github/config/lychee.toml | 2 -- ibm-mq-metrics/README.md | 8 ++++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/config/lychee.toml b/.github/config/lychee.toml index bde272fc9..b2d5285f1 100644 --- a/.github/config/lychee.toml +++ b/.github/config/lychee.toml @@ -7,8 +7,6 @@ max_concurrency = 4 include_fragments = true exclude = [ - # until https://github.com/open-telemetry/opentelemetry-java-contrib/issues/2221 is resolved - "^https?://www.ibm.com", # excluding links to pull requests and issues is done for performance "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$", ] diff --git a/ibm-mq-metrics/README.md b/ibm-mq-metrics/README.md index 5c75acd32..4389661d8 100644 --- a/ibm-mq-metrics/README.md +++ b/ibm-mq-metrics/README.md @@ -9,7 +9,7 @@ program-to-program messaging across multiple platforms. The IBM MQ metrics utility here can monitor multiple queues managers and their resources, namely queues, topics, channels and listeners The metrics are extracted out using the -[PCF command messages](https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm). +[PCF command messages](https://www.ibm.com/docs/en/ibm-mq/8.0.0?topic=tasks-introduction-programmable-command-formats). The metrics for queue manager, queue, topic, channel and listener can be configured. @@ -120,7 +120,7 @@ _Note: The following is only needed for versions of Java 8 before 8u161._ 2. Please add the following JVM arguments to the MA start up command or script. ```-Dcom.ibm.mq.cfg.useIBMCipherMappings=false``` (If you are using IBM Cipher Suites, set the - flag to true. Please visit [this link](http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm) for more details. + flag to true. Please visit [this link](https://www.ibm.com/docs/en/ibm-mq/8.0.0?topic=java-ssltls-cipherspecs-ciphersuites-in-mq-classes) for more details. ) 3. To configure SSL, the MA's trust store and keystore needs to be setup with the JKS filepath. They can be passed either as Machine Agent JVM arguments or configured in config.yml (sslConnection)
@@ -156,12 +156,12 @@ config.yml file in src/main/resources/config.yml. ### Monitoring Workings - Internals -This software extracts metrics through [PCF framework](https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q019990_.htm). +This software extracts metrics through [PCF framework](https://www.ibm.com/docs/en/ibm-mq/8.0.0?topic=tasks-introduction-programmable-command-formats). [A complete list of PCF commands are listed here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q086870_.htm). Each queue manager has an administration queue with a standard queue name and the extension sends PCF command messages to that queue. On Windows and Unix platforms, the PCF commands are sent is always sent to the SYSTEM.ADMIN.COMMAND.QUEUE queue. -[More details mentioned here](https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm) +[More details mentioned here](https://www.ibm.com/docs/en/ibm-mq/8.0.0?topic=formats-pcf-command-messages) By default, the PCF responses are sent to the SYSTEM.DEFAULT.MODEL.QUEUE. Using this queue causes a temporary dynamic queue to be created. You can override the default here by using the From 69b626eb07b3e9d958befd6bcecaf2c7e2472c2a Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 16 Sep 2025 11:23:26 -0700 Subject: [PATCH 292/371] Clearer link checking (#2267) --- .github/workflows/reusable-link-check.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 9cfd9ff48..e891e6d06 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -44,17 +44,15 @@ jobs: | grep -E '^(\.github/config/lychee\.toml|mise\.toml)$' || true) if [ -n "$config_modified" ]; then echo "modified=true" >> $GITHUB_OUTPUT - else - echo "modified=false" >> $GITHUB_OUTPUT fi - - name: Link check - all links (modified files only) - if: github.event_name == 'pull_request' && steps.modified-files.outputs.files != '' + - name: Link check (modified files only) + if: github.event_name == 'pull_request' && steps.modified-files.outputs.files != '' && steps.config-check.outputs.modified != 'true' env: GITHUB_TOKEN: ${{ github.token }} run: mise run lint-links ${{ steps.modified-files.outputs.files }} - - name: Link check - all links (all files) + - name: Link check (all files) if: github.event_name != 'pull_request' || steps.config-check.outputs.modified == 'true' env: GITHUB_TOKEN: ${{ github.token }} From eb34677adbd8dc8fa70982122d666feb0fa91d62 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 17 Sep 2025 08:28:17 +0300 Subject: [PATCH 293/371] fix(deps): update dependency com.uber.nullaway:nullaway to v0.12.10 (#2269) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 89b286c14..d8e819986 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -45,7 +45,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") - api("com.uber.nullaway:nullaway:0.12.9") + api("com.uber.nullaway:nullaway:0.12.10") api("org.assertj:assertj-core:3.27.4") api("org.awaitility:awaitility:4.3.0") api("org.bouncycastle:bcpkix-jdk15on:1.70") From 6c01689ff572def9e1a324b40c2a30c1dcbb2402 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 18 Sep 2025 07:44:12 +0300 Subject: [PATCH 294/371] fix(deps): update all patch versions (#2271) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- gcp-auth-extension/build.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index f72c44034..241bd8a94 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -153,7 +153,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) - implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.2")) + implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.3")) compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index 9803ef080..d7e99ad30 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -24,7 +24,7 @@ dependencies { compileOnly("io.opentelemetry:opentelemetry-exporter-otlp") // Only dependencies added to `implementation` configuration will be picked up by Shadow plugin - implementation("com.google.auth:google-auth-library-oauth2-http:1.39.0") + implementation("com.google.auth:google-auth-library-oauth2-http:1.39.1") // Test dependencies testCompileOnly("com.google.auto.service:auto-service-annotations") From 28d8f2156818c6ce837d8c9ad35de6d248b0f4d3 Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Thu, 18 Sep 2025 15:29:31 +0100 Subject: [PATCH 295/371] opamp exponential backoff retries on http connection failures (#2274) --- .../client/internal/request/service/HttpRequestService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java index e9f79c254..8badaa98f 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/request/service/HttpRequestService.java @@ -161,12 +161,14 @@ private void doSendRequest() { } } catch (IOException | InterruptedException | TimeoutException e) { getCallback().onConnectionFailed(e); + connectionStatus.retryAfter(null); } catch (ExecutionException e) { if (e.getCause() != null) { getCallback().onConnectionFailed(e.getCause()); } else { getCallback().onConnectionFailed(e); } + connectionStatus.retryAfter(null); } } From c2c62aa97383412663fbe198c4259e17ea26ac6b Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Thu, 18 Sep 2025 13:18:37 -0700 Subject: [PATCH 296/371] Empty attribute values in disk buffering (#2268) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .../serialization/mapping/common/AttributesMapper.java | 8 +++++++- .../mapping/common/AttributesMapperTest.java | 1 + .../contrib/disk/buffering/testutils/TestData.java | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapper.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapper.java index e017cb878..631751349 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapper.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapper.java @@ -85,7 +85,13 @@ private static void addValue(AttributesBuilder builder, String key, AnyValue val } else if (value.array_value != null) { addArray(builder, key, value.array_value); } else { - throw new UnsupportedOperationException(); + // Until we have complex attribute types that could potentially yield + // empty objects, we MUST assume here that the writer put an empty string + // into the value of the attribute. This will need to change later, when complex + // types arrive and the spec issue is resolved. + // + // See spec issue: https://github.com/open-telemetry/opentelemetry-specification/issues/4660 + builder.put(AttributeKey.stringKey(key), ""); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java index ceb758642..a5db9382f 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/mapping/common/AttributesMapperTest.java @@ -21,6 +21,7 @@ void verifyMapping() { Attributes attributes = Attributes.builder() .put(AttributeKey.stringKey("someString"), "someValue") + .put(AttributeKey.stringKey("emptyString"), "") .put(AttributeKey.booleanKey("someBool"), true) .put(AttributeKey.longKey("someLong"), 10L) .put(AttributeKey.doubleKey("someDouble"), 10.0) diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java index ec69e8024..0da28971c 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/TestData.java @@ -37,6 +37,8 @@ public final class TestData { .put("conditions", false, true) .put("scores", 0L, 1L) .put("coins", 0.01, 0.05, 0.1) + .put("empty", "") + .put("blank", " ") .build(); public static final Resource RESOURCE_FULL = From f17acde76d304dc97105099a3952e8a81f553924 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 08:13:45 +0300 Subject: [PATCH 297/371] fix(deps): update all patch versions (#2275) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- ibm-mq-metrics/build.gradle.kts | 2 +- .../src/test/resources/projects/springboot_1/pom.xml | 2 +- .../src/test/resources/projects/springboot_2/pom.xml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index d8e819986..e5ffef4bc 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -46,7 +46,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") api("com.uber.nullaway:nullaway:0.12.10") - api("org.assertj:assertj-core:3.27.4") + api("org.assertj:assertj-core:3.27.5") api("org.awaitility:awaitility:4.3.0") api("org.bouncycastle:bcpkix-jdk15on:1.70") api("org.junit-pioneer:junit-pioneer:1.9.1") diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 2370f2893..8645c6d39 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -42,7 +42,7 @@ dependencies { implementation("org.slf4j:slf4j-simple:2.0.17") testImplementation("com.google.guava:guava") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("org.assertj:assertj-core:3.27.4") + integrationTestImplementation("org.assertj:assertj-core:3.27.5") integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4") integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.1") diff --git a/maven-extension/src/test/resources/projects/springboot_1/pom.xml b/maven-extension/src/test/resources/projects/springboot_1/pom.xml index d1c65f6d0..16201b2d9 100644 --- a/maven-extension/src/test/resources/projects/springboot_1/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_1/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.5 + 3.5.6 io.opentelemetry.contrib.maven.test diff --git a/maven-extension/src/test/resources/projects/springboot_2/pom.xml b/maven-extension/src/test/resources/projects/springboot_2/pom.xml index e46320f27..7b594aa8d 100644 --- a/maven-extension/src/test/resources/projects/springboot_2/pom.xml +++ b/maven-extension/src/test/resources/projects/springboot_2/pom.xml @@ -6,7 +6,7 @@ org.springframework.boot spring-boot-starter-parent - 3.5.5 + 3.5.6 io.opentelemetry.contrib.maven.test From 13d1b67d4a6ac32c8788e389c83d6dca3be75a58 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 19 Sep 2025 16:44:41 +0000 Subject: [PATCH 298/371] chore(deps): update gradle to v9.1.0 (#2277) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlew | 3 --- gradlew.bat | 3 +-- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3e781fbad..6a38a8cea 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,7 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionSha256Sum=8fad3d78296ca518113f3d29016617c7f9367dc005f932bd9d93bf45ba46072b -distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip +distributionSha256Sum=a17ddd85a26b6a7f5ddb71ff8b05fc5104c0202c6e64782429790c933686c806 +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew b/gradlew index ef07e0162..adff685a0 100755 --- a/gradlew +++ b/gradlew @@ -114,7 +114,6 @@ case "$( uname )" in #( NONSTOP* ) nonstop=true ;; esac -CLASSPATH="\\\"\\\"" # Determine the Java command to use to start the JVM. @@ -172,7 +171,6 @@ fi # For Cygwin or MSYS, switch paths to Windows format before running java if "$cygwin" || "$msys" ; then APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) - CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) JAVACMD=$( cygpath --unix "$JAVACMD" ) @@ -212,7 +210,6 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ - -classpath "$CLASSPATH" \ -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ "$@" diff --git a/gradlew.bat b/gradlew.bat index db3a6ac20..c4bdd3ab8 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -70,11 +70,10 @@ goto fail :execute @rem Setup the command line -set CLASSPATH= @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* :end @rem End local scope for the variables with windows NT shell From c2beac7eef9f9e2c8bda745a2964da02a36dcb41 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 19 Sep 2025 09:44:41 -0700 Subject: [PATCH 299/371] Add develocity renovate grouping (#2276) --- .github/renovate.json5 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index c6a64fbe0..971d141b2 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -152,6 +152,13 @@ 'com.fasterxml.jackson.core:**', ], }, + { + groupName: 'develocity packages', + matchPackageNames: [ + 'com.gradle.develocity', + 'com.gradle:develocity-gradle-plugin', + ], + }, { // pinned version for compatibility with java 8 JFR parsing matchUpdateTypes: [ From 912d531d2266d0a30176931b4ab220c1983b7dee Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 19 Sep 2025 15:16:49 -0700 Subject: [PATCH 300/371] Add another Renovate grouping (#2279) --- .github/renovate.json5 | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 971d141b2..7367f369f 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -159,6 +159,12 @@ 'com.gradle:develocity-gradle-plugin', ], }, + { + groupName: 'bouncycastle packages', + matchPackageNames: [ + 'com.bouncycastle:**', + ], + }, { // pinned version for compatibility with java 8 JFR parsing matchUpdateTypes: [ From 0cb311d059500027106bad7dc865cee01274d284 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 07:50:48 +0300 Subject: [PATCH 301/371] fix(deps): update dependency com.gradle:develocity-gradle-plugin to v4.2 (#2285) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 03f959388..7460fdcd5 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.3.0") implementation("org.owasp:dependency-check-gradle:12.1.3") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") - implementation("com.gradle:develocity-gradle-plugin:4.1.1") + implementation("com.gradle:develocity-gradle-plugin:4.2") implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6") implementation("com.google.auto.value:auto-value-annotations:1.11.0") } From 4893aaf80f35b9ab016349363b1e0a3075196f0d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 07:51:18 +0300 Subject: [PATCH 302/371] fix(deps): update dependency com.google.guava:guava-bom to v33.5.0-jre (#2284) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 241bd8a94..a849c79c2 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -152,7 +152,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) - implementation(enforcedPlatform("com.google.guava:guava-bom:33.4.8-jre")) + implementation(enforcedPlatform("com.google.guava:guava-bom:33.5.0-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.3")) compileOnly("com.google.auto.value:auto-value-annotations") From 4a8b9b42207266f204b663030f11ad1edefc2a00 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 04:51:56 +0000 Subject: [PATCH 303/371] chore(deps): update plugin com.gradle.develocity to v4.2 (#2283) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 13de5fd7e..d3b0d433d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.gradleup.shadow") version "9.1.0" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.1.1" + id("com.gradle.develocity") version "4.2" } } From c14c05d44d6cb359831d6a6f533fdaad10bde2b0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 04:58:32 +0000 Subject: [PATCH 304/371] chore(deps): update otel/weaver docker tag to v0.18.0 (#2282) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- ibm-mq-metrics/weaver.Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ibm-mq-metrics/weaver.Dockerfile b/ibm-mq-metrics/weaver.Dockerfile index 2acaa8257..92fcad00e 100644 --- a/ibm-mq-metrics/weaver.Dockerfile +++ b/ibm-mq-metrics/weaver.Dockerfile @@ -3,4 +3,4 @@ # Dependabot can keep this file up to date with latest containers. # Weaver is used to generate markdown docs, and enforce policies on the model and run integration tests. -FROM otel/weaver:v0.17.1@sha256:32523b5e44fb44418786347e9f7dde187d8797adb6d57a2ee99c245346c3cdfe AS weaver \ No newline at end of file +FROM otel/weaver:v0.18.0@sha256:5425ade81dc22ddd840902b0638b4b6a9186fb654c5b50c1d1ccd31299437390 AS weaver \ No newline at end of file From ab58f537bea90e7db7b784410f98a7701dea1c94 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:12:48 +0300 Subject: [PATCH 305/371] fix(deps): update dependency org.owasp:dependency-check-gradle to v12.1.5 (#2281) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 7460fdcd5..91b4841e8 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:spotless-plugin-gradle:7.2.1") implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.3.0") - implementation("org.owasp:dependency-check-gradle:12.1.3") + implementation("org.owasp:dependency-check-gradle:12.1.5") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") implementation("com.gradle:develocity-gradle-plugin:4.2") implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6") From cabc9546ed7545d602619b600e740cc5ab9aadc2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 09:31:29 +0300 Subject: [PATCH 306/371] fix(deps): update dependency org.bouncycastle:bcpkix-jdk18on to v1.82 (#2287) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- jmx-scraper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 687b30612..2cee6a13c 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -41,7 +41,7 @@ testing { implementation("com.linecorp.armeria:armeria-grpc") implementation("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") implementation("org.bouncycastle:bcprov-jdk18on:1.81") - implementation("org.bouncycastle:bcpkix-jdk18on:1.81") + implementation("org.bouncycastle:bcpkix-jdk18on:1.82") } } } From 88cde77388e61c010d03a841120256bab0174852 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 22 Sep 2025 08:19:33 -0700 Subject: [PATCH 307/371] fix(deps): update errorprone packages to v2.42.0 (#2289) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Lauri Tulmin --- .../contrib/awsxray/AwsSpanMetricsProcessor.java | 8 ++++---- dependencyManagement/build.gradle.kts | 4 ++-- .../contrib/jmxmetrics/MBeanHelperTest.java | 9 ++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java index 9a3e60519..69ca18476 100644 --- a/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java +++ b/aws-xray/src/main/java/io/opentelemetry/contrib/awsxray/AwsSpanMetricsProcessor.java @@ -153,16 +153,16 @@ private static Long getAwsStatusCode(SpanData spanData) { Throwable throwable = exceptionEvent.getException(); try { - Method method = throwable.getClass().getMethod("getStatusCode", new Class[] {}); - Object code = method.invoke(throwable, new Object[] {}); + Method method = throwable.getClass().getMethod("getStatusCode"); + Object code = method.invoke(throwable); return Long.valueOf((Integer) code); } catch (Exception e) { // Take no action } try { - Method method = throwable.getClass().getMethod("statusCode", new Class[] {}); - Object code = method.invoke(throwable, new Object[] {}); + Method method = throwable.getClass().getMethod("statusCode"); + Object code = method.invoke(throwable); return Long.valueOf((Integer) code); } catch (Exception e) { // Take no action diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index e5ffef4bc..d715f94e1 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -27,8 +27,8 @@ dependencies { api("com.google.auto.service:auto-service-annotations:1.1.1") api("com.google.auto.value:auto-value:1.11.0") api("com.google.auto.value:auto-value-annotations:1.11.0") - api("com.google.errorprone:error_prone_annotations:2.41.0") - api("com.google.errorprone:error_prone_core:2.41.0") + api("com.google.errorprone:error_prone_annotations:2.42.0") + api("com.google.errorprone:error_prone_core:2.42.0") api("io.github.netmikey.logunit:logunit-jul:2.0.0") api("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") api("io.prometheus:simpleclient:0.16.0") diff --git a/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/MBeanHelperTest.java b/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/MBeanHelperTest.java index 6a9886fe8..6fb3660c7 100644 --- a/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/MBeanHelperTest.java +++ b/jmx-metrics/src/test/java/io/opentelemetry/contrib/jmxmetrics/MBeanHelperTest.java @@ -141,7 +141,7 @@ void transform() throws Exception { mBeanHelper.fetch(); assertThat(mBeanHelper.getAttribute("SomeAttribute")) - .hasSameElementsAs(Stream.of(new String[] {"otherValue"}).collect(Collectors.toList())); + .hasSameElementsAs(Stream.of("otherValue").collect(Collectors.toList())); } @Test @@ -169,10 +169,9 @@ void transformMultipleAttributes() throws Exception { mBeanHelper.fetch(); assertThat(mBeanHelper.getAttribute("SomeAttribute")) - .hasSameElementsAs(Stream.of(new String[] {"newValue"}).collect(Collectors.toList())); + .hasSameElementsAs(Stream.of("newValue").collect(Collectors.toList())); assertThat(mBeanHelper.getAttribute("AnotherAttribute")) - .hasSameElementsAs( - Stream.of(new String[] {"anotherNewValue"}).collect(Collectors.toList())); + .hasSameElementsAs(Stream.of("anotherNewValue").collect(Collectors.toList())); } @Test @@ -190,7 +189,7 @@ void customAttribute() throws Exception { mBeanHelper.fetch(); assertThat(mBeanHelper.getAttribute("CustomAttribute")) - .hasSameElementsAs(Stream.of(new String[] {"customValue"}).collect(Collectors.toList())); + .hasSameElementsAs(Stream.of("customValue").collect(Collectors.toList())); } private static void registerThings(String thingName) throws Exception { From a6941c11899febd71ea741d2fed6e86601ffa28f Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Mon, 22 Sep 2025 20:21:57 +0200 Subject: [PATCH 308/371] declarative config: support Span stacktrace (#2262) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: Pranav Sharma --- span-stacktrace/README.md | 16 +++++++- span-stacktrace/build.gradle.kts | 5 +++ .../stacktrace/StackTraceAutoConfig.java | 17 ++++---- .../StackTraceComponentProvider.java | 35 ++++++++++++++++ .../stacktrace/StackTraceSpanProcessor.java | 10 +++++ .../StackTraceComponentProviderTest.java | 41 +++++++++++++++++++ 6 files changed, 115 insertions(+), 9 deletions(-) create mode 100644 span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProvider.java create mode 100644 span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProviderTest.java diff --git a/span-stacktrace/README.md b/span-stacktrace/README.md index 92eef3bbe..ecd31ac85 100644 --- a/span-stacktrace/README.md +++ b/span-stacktrace/README.md @@ -1,4 +1,3 @@ - # Span stacktrace capture This module provides a `SpanProcessor` that captures the [`code.stacktrace`](https://opentelemetry.io/docs/specs/semconv/attributes-registry/code/). @@ -25,6 +24,21 @@ SDK when included in the application runtime dependencies. - value is the class name of a class implementing `java.util.function.Predicate` - filter class must be publicly accessible and provide a no-arg constructor +### Usage with declarative configuration + +You can enable the stacktrace span processor using declarative YAML configuration with the OpenTelemetry SDK. For example: + +```yaml +file_format: 1.0-rc.1 +tracer_provider: + processors: + - experimental_stacktrace: + min_duration: 10 # minimal duration in ms, default is 5, MUST be an integer + filter: my.class.Name # optional, default is to include all spans +``` + +This configuration will register the StackTraceSpanProcessor for all spans. + ## Component owners - [Jack Shirazi](https://github.com/jackshirazi), Elastic diff --git a/span-stacktrace/build.gradle.kts b/span-stacktrace/build.gradle.kts index 4033b0177..57f299754 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-declarative-config-bridge") 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-declarative-config-bridge") compileOnly("io.opentelemetry.semconv:opentelemetry-semconv") testImplementation("io.opentelemetry.semconv:opentelemetry-semconv") 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 3a427b7e3..4417c0f86 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 @@ -26,25 +26,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."; + 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..338ebd960 --- /dev/null +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProvider.java @@ -0,0 +1,35 @@ +/* + * 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.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.CONFIG_MIN_DURATION, "min_duration") + .addMapping(StackTraceAutoConfig.PREFIX, "") + .build(config)); + } + + @Override + public Class getType() { + return SpanProcessor.class; + } +} diff --git a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java index 3565957b1..441e07446 100644 --- a/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java +++ b/span-stacktrace/src/main/java/io/opentelemetry/contrib/stacktrace/StackTraceSpanProcessor.java @@ -94,4 +94,14 @@ private static String removeInternalFrames(String stackTrace) { } return stackTrace.substring(nextNewLine + 1); } + + @Override + public String toString() { + return "StackTraceSpanProcessor{" + + "minSpanDurationNanos=" + + minSpanDurationNanos + + ", filterPredicate=" + + filterPredicate + + '}'; + } } 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..ee55d726c --- /dev/null +++ b/span-stacktrace/src/test/java/io/opentelemetry/contrib/stacktrace/StackTraceComponentProviderTest.java @@ -0,0 +1,41 @@ +/* + * 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 java.util.Locale; +import java.util.concurrent.TimeUnit; +import org.junit.jupiter.api.Test; + +class StackTraceComponentProviderTest { + @Test + void endToEnd() { + String yaml = + "file_format: 1.0-rc.1\n" + + "tracer_provider:\n" + + " processors:\n" + + " - experimental_stacktrace: \n" + + " min_duration: 100\n" + + " filter: io.opentelemetry.contrib.stacktrace.StackTraceSpanProcessorTest$YesPredicate\n"; + + OpenTelemetrySdk openTelemetrySdk = + DeclarativeConfiguration.parseAndCreate( + new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))); + + assertThat(openTelemetrySdk.getSdkTracerProvider().toString()) + .contains( + String.format( + Locale.ROOT, + "StackTraceSpanProcessor{minSpanDurationNanos=%d, " + + "filterPredicate=io.opentelemetry.contrib.stacktrace.StackTraceSpanProcessorTest$YesPredicate", + TimeUnit.MILLISECONDS.toNanos(100))); + } +} From 50281948ad31392be400e5651700692f17d58e98 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 23 Sep 2025 07:47:34 +0300 Subject: [PATCH 309/371] fix(deps): update all patch versions (#2293) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 4 ++-- ibm-mq-metrics/build.gradle.kts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index d715f94e1..2221dfd27 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -2,7 +2,7 @@ plugins { `java-platform` } -val otelInstrumentationVersion = "2.20.0-alpha" +val otelInstrumentationVersion = "2.20.1-alpha" val semconvVersion = "1.37.0" javaPlatform { @@ -46,7 +46,7 @@ dependencies { api("com.google.code.findbugs:annotations:3.0.1u2") api("com.google.code.findbugs:jsr305:3.0.2") api("com.uber.nullaway:nullaway:0.12.10") - api("org.assertj:assertj-core:3.27.5") + api("org.assertj:assertj-core:3.27.6") api("org.awaitility:awaitility:4.3.0") api("org.bouncycastle:bcpkix-jdk15on:1.70") api("org.junit-pioneer:junit-pioneer:1.9.1") diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 8645c6d39..d57b87795 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -42,7 +42,7 @@ dependencies { implementation("org.slf4j:slf4j-simple:2.0.17") testImplementation("com.google.guava:guava") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("org.assertj:assertj-core:3.27.5") + integrationTestImplementation("org.assertj:assertj-core:3.27.6") integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4") integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.1") From 8a27e01f4d7dff92126a84f1e0333224fa13c40f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 23 Sep 2025 14:44:15 -0700 Subject: [PATCH 310/371] Update gradle plugin format (#2292) --- buildSrc/build.gradle.kts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 91b4841e8..9a014c2a6 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -12,13 +12,13 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:spotless-plugin-gradle:7.2.1") - implementation("net.ltgt.gradle:gradle-errorprone-plugin:4.3.0") - implementation("net.ltgt.gradle:gradle-nullaway-plugin:2.3.0") - implementation("org.owasp:dependency-check-gradle:12.1.5") + implementation("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:7.2.1") + implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.3.0") + implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") + implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.5") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") - implementation("com.gradle:develocity-gradle-plugin:4.2") - implementation("me.champeau.gradle:japicmp-gradle-plugin:0.4.6") + implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2") + implementation("me.champeau.gradle.japicmp:me.champeau.gradle.japicmp.gradle.plugin:0.4.6") implementation("com.google.auto.value:auto-value-annotations:1.11.0") } From b373e2e33702288524918ebeab2743cc5b5381cf Mon Sep 17 00:00:00 2001 From: SylvainJuge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 24 Sep 2025 01:19:45 +0200 Subject: [PATCH 311/371] jmx-scraper implement stable service.instance.id (#2270) --- jmx-scraper/build.gradle.kts | 2 + .../contrib/jmxscraper/JmxConnectionTest.java | 39 +++++++- .../contrib/jmxscraper/JmxScraper.java | 90 +++++++++++++------ .../config/PropertiesCustomizer.java | 30 +++++++ .../config/PropertiesCustomizerTest.java | 16 ++-- 5 files changed, 144 insertions(+), 33 deletions(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 2cee6a13c..23087eec0 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -26,6 +26,8 @@ dependencies { implementation("io.opentelemetry.instrumentation:opentelemetry-jmx-metrics") + implementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating") + testImplementation("org.junit-pioneer:junit-pioneer") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("org.awaitility:awaitility") diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java index c984724e3..00d7fc124 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java @@ -9,6 +9,7 @@ import java.nio.file.Path; import java.security.cert.X509Certificate; +import java.util.UUID; import java.util.concurrent.TimeUnit; import java.util.function.Function; import org.junit.jupiter.api.AfterAll; @@ -131,7 +132,7 @@ private void testServerSsl( } @ParameterizedTest - @EnumSource(value = JmxScraperContainer.ConfigSource.class) + @EnumSource void serverSslClientSsl(JmxScraperContainer.ConfigSource configSource) { // Note: this could have been made simpler by relying on the fact that keystore could be used // as a trust store, but having clear split provides also some extra clarity @@ -175,6 +176,42 @@ void serverSslClientSsl(JmxScraperContainer.ConfigSource configSource) { .withConfigSource(configSource)); } + @Test + void stableServiceInstanceServiceId() { + // start a single app, connect twice to it and check that the service id is the same + try (TestAppContainer app = appContainer().withJmxPort(JMX_PORT)) { + app.start(); + + UUID firstId = startScraperAndGetServiceId(); + UUID secondId = startScraperAndGetServiceId(); + + assertThat(firstId) + .describedAs( + "connecting twice to the same JVM should return the same service instance ID") + .isEqualTo(secondId); + } + } + + private static UUID startScraperAndGetServiceId() { + try (JmxScraperContainer scraper = + scraperContainer() + .withRmiServiceUrl(APP_HOST, JMX_PORT) + // does not need to be tested on all config sources + .withConfigSource(JmxScraperContainer.ConfigSource.SYSTEM_PROPERTIES)) { + scraper.start(); + waitTerminated(scraper); + String[] logLines = scraper.getLogs().split("\n"); + UUID serviceId = null; + for (String logLine : logLines) { + if (logLine.contains("remote service instance ID")) { + serviceId = UUID.fromString(logLine.substring(logLine.lastIndexOf(":") + 1).trim()); + } + } + assertThat(serviceId).describedAs("unable to get service instance ID from logs").isNotNull(); + return serviceId; + } + } + private static void connectionTest( Function customizeApp, Function customizeScraper) { diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java index 0835d9d92..678368d7b 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/JmxScraper.java @@ -5,13 +5,14 @@ package io.opentelemetry.contrib.jmxscraper; +import static io.opentelemetry.semconv.incubating.ServiceIncubatingAttributes.SERVICE_INSTANCE_ID; import static java.util.Arrays.asList; import static java.util.Collections.singletonList; -import static java.util.Optional.ofNullable; import static java.util.logging.Level.INFO; import static java.util.logging.Level.SEVERE; -import io.opentelemetry.api.GlobalOpenTelemetry; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.jmxscraper.config.JmxScraperConfig; import io.opentelemetry.contrib.jmxscraper.config.PropertiesCustomizer; import io.opentelemetry.contrib.jmxscraper.config.PropertiesSupplier; @@ -19,23 +20,32 @@ import io.opentelemetry.instrumentation.jmx.engine.MetricConfiguration; import io.opentelemetry.instrumentation.jmx.yaml.RuleParser; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.resources.Resource; import java.io.DataInputStream; import java.io.IOException; import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiFunction; import java.util.logging.Logger; +import javax.annotation.Nullable; import javax.management.MBeanServerConnection; +import javax.management.ObjectName; import javax.management.remote.JMXConnector; public final class JmxScraper { + private static final Logger logger = Logger.getLogger(JmxScraper.class.getName()); private static final String CONFIG_ARG = "-config"; private static final String TEST_ARG = "-test"; @@ -64,36 +74,41 @@ public static void main(String[] args) { Properties argsConfig = argsToConfig(effectiveArgs); propagateToSystemProperties(argsConfig); - // auto-configure and register SDK PropertiesCustomizer configCustomizer = new PropertiesCustomizer(); - AutoConfiguredOpenTelemetrySdk.builder() - .addPropertiesSupplier(new PropertiesSupplier(argsConfig)) - .addPropertiesCustomizer(configCustomizer) - .setResultAsGlobal() - .build(); + // we rely on the config customizer to be executed first to get effective config. + BiFunction resourceCustomizer = + (resource, configProperties) -> { + UUID instanceId = getRemoteServiceInstanceId(configCustomizer.getConnectorBuilder()); + if (resource.getAttribute(SERVICE_INSTANCE_ID) != null || instanceId == null) { + return resource; + } + logger.log(INFO, "remote service instance ID: " + instanceId); + return resource.merge( + Resource.create(Attributes.of(SERVICE_INSTANCE_ID, instanceId.toString()))); + }; + + // auto-configure SDK + OpenTelemetry openTelemetry = + AutoConfiguredOpenTelemetrySdk.builder() + .addPropertiesSupplier(new PropertiesSupplier(argsConfig)) + .addPropertiesCustomizer(configCustomizer) + .addResourceCustomizer(resourceCustomizer) + .build() + .getOpenTelemetrySdk(); + + // scraper configuration and connector builder are built using effective SDK configuration + // thus we have to get it after the SDK is built JmxScraperConfig scraperConfig = configCustomizer.getScraperConfig(); - - long exportSeconds = scraperConfig.getSamplingInterval().toMillis() / 1000; - logger.log(INFO, "metrics export interval (seconds) = " + exportSeconds); - - JmxMetricInsight service = - JmxMetricInsight.createService( - GlobalOpenTelemetry.get(), scraperConfig.getSamplingInterval().toMillis()); - JmxConnectorBuilder connectorBuilder = - JmxConnectorBuilder.createNew(scraperConfig.getServiceUrl()); - - ofNullable(scraperConfig.getUsername()).ifPresent(connectorBuilder::withUser); - ofNullable(scraperConfig.getPassword()).ifPresent(connectorBuilder::withPassword); - - if (scraperConfig.isRegistrySsl()) { - connectorBuilder.withSslRegistry(); - } + JmxConnectorBuilder connectorBuilder = configCustomizer.getConnectorBuilder(); if (testMode) { System.exit(testConnection(connectorBuilder) ? 0 : 1); } else { - JmxScraper jmxScraper = new JmxScraper(connectorBuilder, service, scraperConfig); + JmxMetricInsight jmxInsight = + JmxMetricInsight.createService( + openTelemetry, scraperConfig.getSamplingInterval().toMillis()); + JmxScraper jmxScraper = new JmxScraper(connectorBuilder, jmxInsight, scraperConfig); jmxScraper.start(); } } catch (ConfigurationException e) { @@ -117,7 +132,6 @@ public static void main(String[] args) { private static boolean testConnection(JmxConnectorBuilder connectorBuilder) { try (JMXConnector connector = connectorBuilder.build()) { - MBeanServerConnection connection = connector.getMBeanServerConnection(); Integer mbeanCount = connection.getMBeanCount(); if (mbeanCount > 0) { @@ -133,6 +147,30 @@ private static boolean testConnection(JmxConnectorBuilder connectorBuilder) { } } + @Nullable + private static UUID getRemoteServiceInstanceId(JmxConnectorBuilder connectorBuilder) { + try (JMXConnector jmxConnector = connectorBuilder.build()) { + MBeanServerConnection connection = jmxConnector.getMBeanServerConnection(); + + StringBuilder id = new StringBuilder(); + try { + ObjectName objectName = new ObjectName("java.lang:type=Runtime"); + for (String attribute : Arrays.asList("StartTime", "Name")) { + Object value = connection.getAttribute(objectName, attribute); + if (id.length() > 0) { + id.append(" "); + } + id.append(value); + } + return UUID.nameUUIDFromBytes(id.toString().getBytes(StandardCharsets.UTF_8)); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } catch (IOException e) { + return null; + } + } + // package private for testing static void propagateToSystemProperties(Properties properties) { for (Map.Entry entry : properties.entrySet()) { diff --git a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java index 141ad025f..dc6f06952 100644 --- a/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java +++ b/jmx-scraper/src/main/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizer.java @@ -8,10 +8,13 @@ import static io.opentelemetry.contrib.jmxscraper.config.JmxScraperConfig.JMX_INTERVAL_LEGACY; import static io.opentelemetry.contrib.jmxscraper.config.JmxScraperConfig.METRIC_EXPORT_INTERVAL; +import io.opentelemetry.contrib.jmxscraper.JmxConnectorBuilder; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.function.Function; +import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nullable; @@ -24,6 +27,8 @@ public final class PropertiesCustomizer implements Function apply(ConfigProperties config) { Map result = new HashMap<>(); @@ -44,10 +49,28 @@ public Map apply(ConfigProperties config) { result.put(METRIC_EXPORT_INTERVAL, intervalLegacy + "ms"); } + // scraper config and connector builder must be initialized with the effective SDK configuration + // thus we need to initialize them here and then rely on getter being called after this method. scraperConfig = JmxScraperConfig.fromConfig(config); + connectorBuilder = createConnectorBuilder(scraperConfig); + + long exportSeconds = scraperConfig.getSamplingInterval().toMillis() / 1000; + logger.log(Level.INFO, "metrics export interval (seconds) = " + exportSeconds); + return result; } + private static JmxConnectorBuilder createConnectorBuilder(JmxScraperConfig scraperConfig) { + JmxConnectorBuilder connectorBuilder = + JmxConnectorBuilder.createNew(scraperConfig.getServiceUrl()); + Optional.ofNullable(scraperConfig.getUsername()).ifPresent(connectorBuilder::withUser); + Optional.ofNullable(scraperConfig.getPassword()).ifPresent(connectorBuilder::withPassword); + if (scraperConfig.isRegistrySsl()) { + connectorBuilder.withSslRegistry(); + } + return connectorBuilder; + } + /** * Get scraper configuration from the previous call to {@link #apply(ConfigProperties)} * @@ -60,4 +83,11 @@ public JmxScraperConfig getScraperConfig() { } return scraperConfig; } + + public JmxConnectorBuilder getConnectorBuilder() { + if (connectorBuilder == null) { + throw new IllegalStateException("apply() must be called before getConnectorBuilder()"); + } + return connectorBuilder; + } } diff --git a/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizerTest.java b/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizerTest.java index 6ee1e7ba1..77ce948cc 100644 --- a/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizerTest.java +++ b/jmx-scraper/src/test/java/io/opentelemetry/contrib/jmxscraper/config/PropertiesCustomizerTest.java @@ -17,16 +17,20 @@ class PropertiesCustomizerTest { + private static final String DUMMY_URL = "service:jmx:rmi:///jndi/rmi://host:999/jmxrmi"; + @Test - void tryGetConfigBeforeApply() { + void tryGetBeforeApply() { assertThatThrownBy(() -> new PropertiesCustomizer().getScraperConfig()) .isInstanceOf(IllegalStateException.class); + assertThatThrownBy(() -> new PropertiesCustomizer().getConnectorBuilder()) + .isInstanceOf(IllegalStateException.class); } @Test void defaultOtlpExporter() { Map map = new HashMap<>(); - map.put("otel.jmx.service.url", "dummy-url"); + map.put("otel.jmx.service.url", DUMMY_URL); map.put("otel.jmx.target.system", "jvm"); ConfigProperties config = DefaultConfigProperties.createFromMap(map); @@ -37,7 +41,7 @@ void defaultOtlpExporter() { @Test void explicitExporterSet() { Map map = new HashMap<>(); - map.put("otel.jmx.service.url", "dummy-url"); + map.put("otel.jmx.service.url", DUMMY_URL); map.put("otel.jmx.target.system", "jvm"); map.put("otel.metrics.exporter", "otlp,logging"); ConfigProperties config = DefaultConfigProperties.createFromMap(map); @@ -49,7 +53,7 @@ void explicitExporterSet() { @Test void getSomeConfiguration() { Map map = new HashMap<>(); - map.put("otel.jmx.service.url", "dummy-url"); + map.put("otel.jmx.service.url", DUMMY_URL); map.put("otel.jmx.target.system", "jvm"); map.put("otel.metrics.exporter", "otlp"); ConfigProperties config = DefaultConfigProperties.createFromMap(map); @@ -67,7 +71,7 @@ void getSomeConfiguration() { @Test void setSdkMetricExportFromJmxInterval() { Map map = new HashMap<>(); - map.put("otel.jmx.service.url", "dummy-url"); + map.put("otel.jmx.service.url", DUMMY_URL); map.put("otel.jmx.target.system", "jvm"); map.put("otel.metrics.exporter", "otlp"); map.put("otel.jmx.interval.milliseconds", "10000"); @@ -83,7 +87,7 @@ void setSdkMetricExportFromJmxInterval() { @Test void sdkMetricExportIntervalPriority() { Map map = new HashMap<>(); - map.put("otel.jmx.service.url", "dummy-url"); + map.put("otel.jmx.service.url", DUMMY_URL); map.put("otel.jmx.target.system", "jvm"); map.put("otel.metrics.exporter", "otlp"); map.put("otel.jmx.interval.milliseconds", "10000"); From 595a3fff2b61d61fa4d67aee2817c6f5de756b77 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Tue, 23 Sep 2025 16:33:53 -0700 Subject: [PATCH 312/371] Add back yaml templates used to generate the default config an example (#2123) Co-authored-by: github-actions[bot] --- ibm-mq-metrics/config.yml | 213 ++++++++++++++++++ .../templates/registry/yaml/config.yml.j2 | 125 ++++++++++ .../templates/registry/yaml/weaver.yaml | 4 + 3 files changed, 342 insertions(+) create mode 100644 ibm-mq-metrics/config.yml create mode 100644 ibm-mq-metrics/templates/registry/yaml/config.yml.j2 create mode 100644 ibm-mq-metrics/templates/registry/yaml/weaver.yaml diff --git a/ibm-mq-metrics/config.yml b/ibm-mq-metrics/config.yml new file mode 100644 index 000000000..61a444fd6 --- /dev/null +++ b/ibm-mq-metrics/config.yml @@ -0,0 +1,213 @@ +#Run it as a scheduled task instead of running every minute. +#If you want to run this every minute, comment this out +#taskSchedule: +# numberOfThreads: 20 +# taskDelaySeconds: 300 + +#This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. +#No need to change the default unless you know what you are doing. +#queueMetricsCollectionTimeoutInSeconds: 40 +#channelMetricsCollectionTimeoutInSeconds: 40 +#topicMetricsCollectionTimeoutInSeconds: 40 + +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + # Indicate the MaxActiveChannels as set in qm.ini, see https://www.ibm.com/docs/en/ibm-mq/9.3.x?topic=qmini-channels-stanza-file + maxActiveChannels: 4200 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Bindings" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + #channelName: "SYSTEM.ADMIN.SVRCONN" + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + #username: "" + #password: "" + + #PCF requests are always sent to SYSTEM.ADMIN.COMMAND.QUEUE. The PCF responses to these requests are sent to the default reply-to queue called + #SYSTEM.DEFAULT.MODEL.QUEUE. However, you can override this behavior and send it to a temporary dynamic queue by changing the modelQueueName and replyQueuePrefix fields. + #For more details around this https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm & https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm + #modelQueueName: "" + #replyQueuePrefix: "" + + # Name of the temporary dynamic queue holding the configuration events. This queue contains information regarding the configuration of the queue manager, notable MaxChannels and MaxActiveChannels. + # If unset, the default queue name `SYSTEM.ADMIN.CONFIG.EVENT` is applied. + # Configuration events need to be enabled explicitly in the queue manager configuration. See https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=monitoring-configuration-events for reference. + #configurationQueueName: "SYSTEM.ADMIN.CONFIG.EVENT" + + # Interval in milliseconds at which the configuration events in the configuration queue can be consumed. + # By default, no events are consumed. + #consumeConfigurationEventInterval: 600000 # 10 minutes + + # Enable running a queue manager refresh request to reload its configuration and create a configuration event. + # This action is only executed if no configuration events are found when reading the configuration queue.name: + # By default, this action is disabled. + #refreshQueueManagerConfigurationEnabled: false + + #Sets the CCSID used in the message descriptor of request and response messages. The default value is MQC.MQCCSI_Q_MGR. + #To set this, please use the integer value. + #ccsid: + + #Sets the encoding used in the message descriptor of request and response messages. The default value is MQC.MQENC_NATIVE. + #To set this, please use the integer value. + #encoding: + + # IBM Cipher Suite e.g. "SSL_RSA_WITH_AES_128_CBC_SHA256".. + # For translation to IBM Cipher http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm + # A cipher working for IBM Cloud MQ and Temurin JDK 8 is TLS_AES_128_GCM_SHA256 + #cipherSuite: "" + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","$SYS"] + +metrics: + "ibm.mq.message.retry.count": # Number of message retries + enabled: true + "ibm.mq.status": # Channel status + enabled: true + "ibm.mq.max.sharing.conversations": # Maximum number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.current.sharing.conversations": # Current number of conversations permitted on this channel instance. + enabled: true + "ibm.mq.byte.received": # Number of bytes received + enabled: true + "ibm.mq.byte.sent": # Number of bytes sent + enabled: true + "ibm.mq.buffers.received": # Buffers received + enabled: true + "ibm.mq.buffers.sent": # Buffers sent + enabled: true + "ibm.mq.message.count": # Message count + enabled: true + "ibm.mq.open.input.count": # Count of applications sending messages to the queue + enabled: true + "ibm.mq.open.output.count": # Count of applications consuming messages from the queue + enabled: true + "ibm.mq.high.queue.depth": # The current high queue depth + enabled: true + "ibm.mq.service.interval": # The queue service interval + enabled: true + "ibm.mq.queue.depth.full.event": # The number of full queue events + enabled: true + "ibm.mq.queue.depth.high.event": # The number of high queue events + enabled: true + "ibm.mq.queue.depth.low.event": # The number of low queue events + enabled: true + "ibm.mq.uncommitted.messages": # Number of uncommitted messages + enabled: true + "ibm.mq.oldest.msg.age": # Queue message oldest age + enabled: true + "ibm.mq.current.max.queue.filesize": # Current maximum queue file size + enabled: true + "ibm.mq.current.queue.filesize": # Current queue file size + enabled: true + "ibm.mq.instances.per.client": # Instances per client + enabled: true + "ibm.mq.message.deq.count": # Message dequeue count + enabled: true + "ibm.mq.message.enq.count": # Message enqueue count + enabled: true + "ibm.mq.queue.depth": # Current queue depth + enabled: true + "ibm.mq.service.interval.event": # Queue service interval event + enabled: true + "ibm.mq.reusable.log.size": # The amount of space occupied, in megabytes, by log extents available to be reused. + enabled: true + "ibm.mq.manager.active.channels": # The queue manager active maximum channels limit + enabled: true + "ibm.mq.restart.log.size": # Size of the log data required for restart recovery in megabytes. + enabled: true + "ibm.mq.max.queue.depth": # Maximum queue depth + enabled: true + "ibm.mq.onqtime.short_period": # Amount of time, in microseconds, that a message spent on the queue, over a short period + enabled: true + "ibm.mq.onqtime.long_period": # Amount of time, in microseconds, that a message spent on the queue, over a longer period + enabled: true + "ibm.mq.message.received.count": # Number of messages received + enabled: true + "ibm.mq.message.sent.count": # Number of messages sent + enabled: true + "ibm.mq.max.instances": # Max channel instances + enabled: true + "ibm.mq.connection.count": # Active connections count + enabled: true + "ibm.mq.manager.status": # Queue manager status + enabled: true + "ibm.mq.heartbeat": # Queue manager heartbeat + enabled: true + "ibm.mq.archive.log.size": # Queue manager archive log size + enabled: true + "ibm.mq.manager.max.active.channels": # Queue manager max active channels + enabled: true + "ibm.mq.manager.statistics.interval": # Queue manager statistics interval + enabled: true + "ibm.mq.publish.count": # Topic publication count + enabled: true + "ibm.mq.subscription.count": # Topic subscription count + enabled: true + "ibm.mq.listener.status": # Listener status + enabled: true + "ibm.mq.unauthorized.event": # Number of authentication error events + enabled: true + "ibm.mq.manager.max.handles": # Max open handles + enabled: true + +sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: http://localhost:4318 \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 b/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 new file mode 100644 index 000000000..762f89446 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 @@ -0,0 +1,125 @@ +#Run it as a scheduled task instead of running every minute. +#If you want to run this every minute, comment this out +#taskSchedule: +# numberOfThreads: 20 +# taskDelaySeconds: 300 + +#This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. +#No need to change the default unless you know what you are doing. +#queueMetricsCollectionTimeoutInSeconds: 40 +#channelMetricsCollectionTimeoutInSeconds: 40 +#topicMetricsCollectionTimeoutInSeconds: 40 + +queueManagers: + - name: "QM1" + host: "localhost" + port: 1414 + + # Indicate the MaxActiveChannels as set in qm.ini, see https://www.ibm.com/docs/en/ibm-mq/9.3.x?topic=qmini-channels-stanza-file + maxActiveChannels: 4200 + + #The transport type for the queue manager connection, the default is "Bindings" for a binding type connection + #For bindings type, connection WMQ extension (i.e machine agent) need to be on the same machine on which WebbsphereMQ server is running + #For client type, connection change it to "Client". + transportType: "Bindings" + + #Channel name of the queue manager, channel should be server-conn type. + #This field is not required in case of transportType: Bindings + #channelName: "SYSTEM.ADMIN.SVRCONN" + + #for user access level, please check "Access Permissions" section on the extensions page + #comment out the username and password in case of transportType: Bindings. + #username: "" + #password: "" + + #PCF requests are always sent to SYSTEM.ADMIN.COMMAND.QUEUE. The PCF responses to these requests are sent to the default reply-to queue called + #SYSTEM.DEFAULT.MODEL.QUEUE. However, you can override this behavior and send it to a temporary dynamic queue by changing the modelQueueName and replyQueuePrefix fields. + #For more details around this https://www.ibm.com/support/knowledgecenter/SSFKSJ_7.5.0/com.ibm.mq.ref.adm.doc/q083240_.htm & https://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.adm.doc/q020010_.htm + #modelQueueName: "" + #replyQueuePrefix: "" + + # Name of the temporary dynamic queue holding the configuration events. This queue contains information regarding the configuration of the queue manager, notable MaxChannels and MaxActiveChannels. + # If unset, the default queue name `SYSTEM.ADMIN.CONFIG.EVENT` is applied. + # Configuration events need to be enabled explicitly in the queue manager configuration. See https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=monitoring-configuration-events for reference. + #configurationQueueName: "SYSTEM.ADMIN.CONFIG.EVENT" + + # Interval in milliseconds at which the configuration events in the configuration queue can be consumed. + # By default, no events are consumed. + #consumeConfigurationEventInterval: 600000 # 10 minutes + + # Enable running a queue manager refresh request to reload its configuration and create a configuration event. + # This action is only executed if no configuration events are found when reading the configuration queue.name: + # By default, this action is disabled. + #refreshQueueManagerConfigurationEnabled: false + + #Sets the CCSID used in the message descriptor of request and response messages. The default value is MQC.MQCCSI_Q_MGR. + #To set this, please use the integer value. + #ccsid: + + #Sets the encoding used in the message descriptor of request and response messages. The default value is MQC.MQENC_NATIVE. + #To set this, please use the integer value. + #encoding: + + # IBM Cipher Suite e.g. "SSL_RSA_WITH_AES_128_CBC_SHA256".. + # For translation to IBM Cipher http://www.ibm.com/support/knowledgecenter/SSFKSJ_8.0.0/com.ibm.mq.dev.doc/q113210_.htm + # A cipher working for IBM Cloud MQ and Temurin JDK 8 is TLS_AES_128_GCM_SHA256 + #cipherSuite: "" + + queueFilters: + #Can provide complete queue name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","AMQ"] + + + channelFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + listenerFilters: + #Can provide complete channel name or generic names. A generic name is a character string followed by an asterisk (*), + #for example ABC*, and it selects all objects having names that start with the selected character string. + #An asterisk on its own matches all possible names. + include: ["*"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM"] + + topicFilters: + # For topics, IBM MQ uses the topic wildcard characters ('#' and '+') and does not treat a trailing asterisk as a wildcard + # https://www.ibm.com/support/knowledgecenter/en/SSFKSJ_7.5.0/com.ibm.mq.pla.doc/q005020_.htm + include: ["#"] + exclude: + #type value: STARTSWITH, EQUALS, ENDSWITH, CONTAINS + - type: "STARTSWITH" + #The name of the queue or queue name pattern as per queue filter, comma separated values + values: ["SYSTEM","$SYS"] + +metrics: +{% for metric in ctx %} {{ metric.metric_name }}: # {{ metric.brief | safe }} + enabled: true +{% endfor %} +sslConnection: + trustStorePath: "" + trustStorePassword: "" + + keyStorePath: "" + keyStorePassword: "" + +# Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ +otlpExporter: + otel.exporter.otlp.endpoint: http://localhost:4318 \ No newline at end of file diff --git a/ibm-mq-metrics/templates/registry/yaml/weaver.yaml b/ibm-mq-metrics/templates/registry/yaml/weaver.yaml new file mode 100644 index 000000000..43a70f633 --- /dev/null +++ b/ibm-mq-metrics/templates/registry/yaml/weaver.yaml @@ -0,0 +1,4 @@ +templates: + - pattern: config.yml.j2 + filter: '.groups | map(select(.type == "metric"))' + application_mode: single \ No newline at end of file From 6faec63b8c55528a1e108a4311e03cd8289df8c5 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 24 Sep 2025 10:31:47 -0700 Subject: [PATCH 313/371] clarify documentation (#2298) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- ibm-mq-metrics/README.md | 14 ++++++++++++-- ibm-mq-metrics/config.yml | 12 ++++++------ .../templates/registry/yaml/config.yml.j2 | 12 ++++++------ 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/ibm-mq-metrics/README.md b/ibm-mq-metrics/README.md index 4389661d8..ced5c3530 100644 --- a/ibm-mq-metrics/README.md +++ b/ibm-mq-metrics/README.md @@ -83,6 +83,17 @@ java \ ./my-config.yml ``` +## Generate code with Weaver + +Weaver generates code, documentation and configuration for this program. + +```shell +make generate +``` + +This generates `config.yaml`, the `docs` folder, the `src/main/java/io/opentelemetry/ibm/mq/metrics` +Java code folder. + ## Connection There are two transport modes in which this extension can be run: @@ -147,12 +158,11 @@ _Note: The following is only needed for versions of Java 8 before 8u161._ **Note** : Please make sure to not use tab (\t) while editing yaml files. You may want to validate the yaml file using a [yaml validator](https://jsonformatter.org/yaml-validator). Configure the monitor by copying and editing the -config.yml file in src/main/resources/config.yml. +config.yml file. 1. Configure the queueManagers with appropriate fields and filters. You can configure multiple queue managers in one configuration file. 2. To run the extension at a frequency > 1 minute, please configure the taskSchedule section. - Refer to the [Task Schedule](https://community.appdynamics.com/t5/Knowledge-Base/Task-Schedule-for-Extensions/ta-p/35414) doc for details. ### Monitoring Workings - Internals diff --git a/ibm-mq-metrics/config.yml b/ibm-mq-metrics/config.yml index 61a444fd6..603330f65 100644 --- a/ibm-mq-metrics/config.yml +++ b/ibm-mq-metrics/config.yml @@ -1,8 +1,8 @@ -#Run it as a scheduled task instead of running every minute. -#If you want to run this every minute, comment this out -#taskSchedule: -# numberOfThreads: 20 -# taskDelaySeconds: 300 +# This section defines the schedule at which the program will scrape metrics. +taskSchedule: + numberOfThreads: 20 + initialDelaySeconds: 0 + taskDelaySeconds: 60 #This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. #No need to change the default unless you know what you are doing. @@ -210,4 +210,4 @@ sslConnection: # Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ otlpExporter: - otel.exporter.otlp.endpoint: http://localhost:4318 \ No newline at end of file + otel.exporter.otlp.endpoint: http://localhost:4318 diff --git a/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 b/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 index 762f89446..5f23b27d0 100644 --- a/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 +++ b/ibm-mq-metrics/templates/registry/yaml/config.yml.j2 @@ -1,8 +1,8 @@ -#Run it as a scheduled task instead of running every minute. -#If you want to run this every minute, comment this out -#taskSchedule: -# numberOfThreads: 20 -# taskDelaySeconds: 300 +# This section defines the schedule at which the program will scrape metrics. +taskSchedule: + numberOfThreads: 20 + initialDelaySeconds: 0 + taskDelaySeconds: 60 #This is the timeout on queue metrics and channel metrics threads.Default value is 20 seconds. #No need to change the default unless you know what you are doing. @@ -122,4 +122,4 @@ sslConnection: # Configure the OTLP exporter using system properties keys following the specification https://opentelemetry.io/docs/languages/java/configuration/ otlpExporter: - otel.exporter.otlp.endpoint: http://localhost:4318 \ No newline at end of file + otel.exporter.otlp.endpoint: http://localhost:4318 From 5f6e742f20c01b544dd2c902271f2e47b7ff3177 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 24 Sep 2025 15:07:27 -0700 Subject: [PATCH 314/371] Fix renovate groupings (#2291) --- .github/renovate.json5 | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 7367f369f..82f86cdf7 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -155,14 +155,13 @@ { groupName: 'develocity packages', matchPackageNames: [ - 'com.gradle.develocity', - 'com.gradle:develocity-gradle-plugin', + 'com.gradle.develocity:**', ], }, { groupName: 'bouncycastle packages', matchPackageNames: [ - 'com.bouncycastle:**', + 'org.bouncycastle:**', ], }, { From ff4a6644f5a596ae5657d3b1beb67be6b3c2260a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 02:05:42 +0000 Subject: [PATCH 315/371] fix(deps): update dependency org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin to v12.1.6 (#2299) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 9a014c2a6..aa1859d59 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:7.2.1") implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.3.0") implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") - implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.5") + implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.6") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2") implementation("me.champeau.gradle.japicmp:me.champeau.gradle.japicmp.gradle.plugin:0.4.6") From 4ff1ffb1173764eb6977706375cfe6f024360a03 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Thu, 25 Sep 2025 16:05:19 -0700 Subject: [PATCH 316/371] Prepare CHANGELOG.md for 1.50.0 (#2300) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: Trask Stalnaker --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f34222ca..9b9daf1fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,63 @@ ## Unreleased +Note: This release broadly applies some style guidelines across the repository. As a result, +some classes that were visible might be package/private. Other non-final classes may now +be final. See +[#2182](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2182) +and +[#2210](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2210) +and +[#2212](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2212) +and +[#2213](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2212) +for examples and details. These changes are not expected to break anyone, so please open +an issue if this causes problems. + +### Baggage processor + +- Move baggage processor to the front of the processor list + ([#2152](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2152)) +- Add declarative configuration support + ([#2031](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2031)) + +### Disk buffering + +- Catching IllegalStateException in case of failed deserialization + ([#2157](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2157)) +- Apply final to public API classes where possible + ([#2216](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2216)) +- Handle empty attribute values + ([#2268](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2268)) + +### Inferred spans + +- Support dynamically changing the inferred span interval + ([#2153](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2153)) + +### JMX scraper + +- Implement stable `service.instance.id` + ([#2270](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2270)) + +### Kafka exporter + +- Add Kafka connectivity error handling + ([#2202](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2202)) + +### OpAMP client + +- Move important user-facing classes out of 'internal' package + ([#2249](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2249)) +- Exponential backoff retries on http connection failures + ([#2274](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2274)) + +### Span stack traces + +- Add declarative configuration support + ([#2262](https://github.com/open-telemetry/opentelemetry-java-contrib/pull/2262)) + + ## Version 1.49.0 (2025-08-25) ### Consistent sampling From ee20753a6504c3ad5e8969b2d703dfdf8109563c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 25 Sep 2025 19:01:40 -0700 Subject: [PATCH 317/371] fix(deps): update all patch versions (#2303) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index d22ef1ab5..e45343149 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Initialize CodeQL - uses: github/codeql-action/init@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 + uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 + uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c186d16e2..311c37958 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@192325c86100d080feab897ff886c34abd4c83a3 # v3.30.3 + uses: github/codeql-action/upload-sarif@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 with: sarif_file: results.sarif diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index a849c79c2..8a72f5dc1 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -153,7 +153,7 @@ testing { implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.5.0-jre")) - implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.3")) + implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.4")) compileOnly("com.google.auto.value:auto-value-annotations") compileOnly("com.google.errorprone:error_prone_annotations") From 9939235a9cf024ba569bace7c9da1a931bdf9a7a Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 26 Sep 2025 17:06:38 +0200 Subject: [PATCH 318/371] update lychee and fix gh links with remap (#2304) --- .github/config/lychee.toml | 5 +++++ CHANGELOG.md | 2 +- consistent-sampling/README.md | 12 ++++++------ mise.toml | 2 +- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/config/lychee.toml b/.github/config/lychee.toml index b2d5285f1..071c66fd8 100644 --- a/.github/config/lychee.toml +++ b/.github/config/lychee.toml @@ -6,6 +6,11 @@ max_concurrency = 4 # Check link anchors include_fragments = true +remap = [ + # workaround for https://github.com/lycheeverse/lychee/issues/1729 + "https://github.com/(.*?)/(.*?)/blob/(.*?)/(.*#.*)$ https://raw.githubusercontent.com/$1/$2/$3/$4" +] + exclude = [ # excluding links to pull requests and issues is done for performance "^https://github.com/open-telemetry/opentelemetry-java-contrib/(issues|pull)/\\d+$", diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b9daf1fa..6c16726a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -327,7 +327,7 @@ The extension takes care of the necessary configuration required to authenticate The future of the [JMX metrics](./jmx-metrics/README.md) component, built on top of the -[JMX metrics](https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/instrumentation/jmx-metrics#jmx-metric-insight) +[JMX metrics](https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/instrumentation/jmx-metrics/README.md#jmx-metric-insight) component from the opentelemetry-java-instrumentation repository. ### Maven extension diff --git a/consistent-sampling/README.md b/consistent-sampling/README.md index 4f848eb3a..49f4c0565 100644 --- a/consistent-sampling/README.md +++ b/consistent-sampling/README.md @@ -5,7 +5,7 @@ There are two major components included here. ## Original proposal implementation The original specification for consistent probability sampling is defined by - + and . It supports sampling probabilities that are power of 2 (1, 1/2, 1/4, ...), and uses 8-bit `r-value` and 8-bit `p-value` in tracestate. @@ -14,18 +14,18 @@ The implementation of this proposal is contained by the package `io/opentelemetr * **ConsistentSampler**: abstract base class of all consistent sampler implementations below * **ConsistentAlwaysOffSampler**: - see + see * **ConsistentAlwaysOnSampler**: - see + see * **ConsistentComposedAndSampler**: allows combining two consistent samplers and samples when both samplers would sample * **ConsistentComposedOrSampler**: allows combining two consistent sampler and samples when at least one of both samplers would sample, - see + see * **ConsistentParentBasedSampler**: - see + see * **ConsistentProbabilityBasedSampler**: - see + see * **ConsistentRateLimitingSampler**: a rate limiting sampler based on exponential smoothing that dynamically adjusts the sampling probability based on the estimated rate of spans occurring to satisfy a given rate of sampled spans diff --git a/mise.toml b/mise.toml index e00ac63b2..bc7b9444a 100644 --- a/mise.toml +++ b/mise.toml @@ -1,5 +1,5 @@ [tools] -lychee = "0.18.1" +lychee = "0.20.1" [tasks.lint-local-links] run = 'lychee --verbose --scheme file --include-fragments {{arg(name="files", default=".")}}' From 837d65ac125672fc925f0fa29e0183e2f6a10fb4 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 26 Sep 2025 09:42:36 -0700 Subject: [PATCH 319/371] Fix release workflow (#2306) --- .github/scripts/update-version.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/scripts/update-version.sh b/.github/scripts/update-version.sh index 2ec064776..39873d0f1 100755 --- a/.github/scripts/update-version.sh +++ b/.github/scripts/update-version.sh @@ -10,3 +10,5 @@ fi sed -Ei "s/val stableVersion = \"[^\"]*\"/val stableVersion = \"$version\"/" version.gradle.kts sed -Ei "s/val alphaVersion = \"[^\"]*\"/val alphaVersion = \"$alpha_version\"/" version.gradle.kts + +sed -Ei "1 s/(Comparing source compatibility of [a-z-]+)-[0-9]+\.[0-9]+\.[0-9]+(-SNAPSHOT)?.jar/\1-$version.jar/" docs/apidiffs/current_vs_latest/*.txt From 30d955cc970ada3d136a2d026d7266a93c3f6b0e Mon Sep 17 00:00:00 2001 From: "otelbot[bot]" <197425009+otelbot[bot]@users.noreply.github.com> Date: Fri, 26 Sep 2025 10:41:01 -0700 Subject: [PATCH 320/371] Update version to 1.51.0-SNAPSHOT (#2307) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- CHANGELOG.md | 2 ++ docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt | 2 +- version.gradle.kts | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c16726a8..1576667f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## Unreleased +## Version 1.50.0 (2025-09-26) + Note: This release broadly applies some style guidelines across the repository. As a result, some classes that were visible might be package/private. Other non-final classes may now be final. See diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt b/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt index fb005385c..08f4319a6 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-aws-xray-1.50.0-SNAPSHOT.jar against opentelemetry-aws-xray-1.49.0.jar +Comparing source compatibility of opentelemetry-aws-xray-1.51.0-SNAPSHOT.jar against opentelemetry-aws-xray-1.49.0.jar No changes. \ No newline at end of file diff --git a/version.gradle.kts b/version.gradle.kts index 9ea6b805e..54918ebc5 100644 --- a/version.gradle.kts +++ b/version.gradle.kts @@ -1,5 +1,5 @@ -val stableVersion = "1.50.0-SNAPSHOT" -val alphaVersion = "1.50.0-alpha-SNAPSHOT" +val stableVersion = "1.51.0-SNAPSHOT" +val alphaVersion = "1.51.0-alpha-SNAPSHOT" allprojects { if (findProperty("otel.stable") != "true") { From 9fb24935a6d152d8b0c1c5e039510b00a42d578f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 26 Sep 2025 12:59:51 -0700 Subject: [PATCH 321/371] Post-release updates for 1.50.0 (#2310) --- docs/apidiffs/1.50.0_vs_1.49.0/opentelemetry-aws-xray.txt | 2 ++ docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/apidiffs/1.50.0_vs_1.49.0/opentelemetry-aws-xray.txt diff --git a/docs/apidiffs/1.50.0_vs_1.49.0/opentelemetry-aws-xray.txt b/docs/apidiffs/1.50.0_vs_1.49.0/opentelemetry-aws-xray.txt new file mode 100644 index 000000000..674d298c5 --- /dev/null +++ b/docs/apidiffs/1.50.0_vs_1.49.0/opentelemetry-aws-xray.txt @@ -0,0 +1,2 @@ +Comparing source compatibility of opentelemetry-aws-xray-1.50.0.jar against opentelemetry-aws-xray-1.49.0.jar +No changes. \ No newline at end of file diff --git a/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt b/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt index 08f4319a6..6c4fe5eda 100644 --- a/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt +++ b/docs/apidiffs/current_vs_latest/opentelemetry-aws-xray.txt @@ -1,2 +1,2 @@ -Comparing source compatibility of opentelemetry-aws-xray-1.51.0-SNAPSHOT.jar against opentelemetry-aws-xray-1.49.0.jar +Comparing source compatibility of opentelemetry-aws-xray-1.51.0-SNAPSHOT.jar against opentelemetry-aws-xray-1.50.0.jar No changes. \ No newline at end of file From 42d57375be6a9d776c81db8564ff39ac2e2625ed Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 26 Sep 2025 13:13:43 -0700 Subject: [PATCH 322/371] Improve issue and PR backlog management (#2305) --- .../issue-management-stale-action.yml | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index 7282d3fcd..ec58a1db8 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -4,6 +4,7 @@ on: schedule: # hourly at minute 23 - cron: "23 * * * *" + workflow_dispatch: permissions: contents: read @@ -18,19 +19,34 @@ jobs: steps: - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} days-before-stale: 7 days-before-close: 7 only-labels: "needs author feedback" stale-issue-label: stale stale-issue-message: > - This has been automatically marked as stale because it has been marked - as needing author feedback and has not had any activity for 7 days. - It will be closed automatically if there is no response from the author - within 7 additional days from this comment. + This issue has been labeled as stale due to lack of activity and needing author feedback. + It will be automatically closed if there is no further activity over the next 7 days. stale-pr-label: stale stale-pr-message: > - This has been automatically marked as stale because it has been marked - as needing author feedback and has not had any activity for 7 days. - It will be closed automatically if there is no response from the author - within 7 additional days from this comment. + This PR has been labeled as stale due to lack of activity and needing author feedback. + It will be automatically closed if there is no further activity over the next 7 days. + + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + with: + days-before-stale: -1 + days-before-close: 365 + close-issue-label: stale + close-issue-message: > + Since there has been no activity on this enhancement for the past year we are closing it to help maintain our backlog. + Anyone who would like to work on it is still welcome to do so, and we can re-open it at that time. + only-labels: "enhancement" + + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + with: + days-before-pr-stale: 90 + days-before-pr-close: 14 + stale-pr-label: stale + stale-pr-message: > + This PR has been labeled as stale due to lack of activity. + It will be automatically closed if there is no further activity over the next 14 days. + exempt-draft-pr: false From 24a30ac212861c0c15150eff3bf9db21f12b3545 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 26 Sep 2025 13:48:39 -0700 Subject: [PATCH 323/371] Fix stale workflow (#2311) --- .github/workflows/issue-management-stale-action.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index ec58a1db8..d3bc49d51 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -39,10 +39,14 @@ jobs: close-issue-message: > Since there has been no activity on this enhancement for the past year we are closing it to help maintain our backlog. Anyone who would like to work on it is still welcome to do so, and we can re-open it at that time. + days-before-pr-stale: -1 + days-before-pr-close: -1 only-labels: "enhancement" - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: + days-before-issue-stale: -1 + days-before-issue-close: -1 days-before-pr-stale: 90 days-before-pr-close: 14 stale-pr-label: stale From 4b3afb6018aec695e9a0b6559d98eaca336d2bad Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 26 Sep 2025 13:49:41 -0700 Subject: [PATCH 324/371] Escape back-ticks (#2309) --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 68800fa37..3bbad7990 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -250,7 +250,7 @@ jobs: GH_TOKEN: ${{ steps.otelbot-token.outputs.token }} run: | message="Post-release updates for $VERSION" - body="Post-release updates for `$VERSION`." + body="Post-release updates for \`$VERSION\`." branch="otelbot/update-apidiff-baseline-to-released-version-${VERSION}" git checkout -b $branch From dc6470f6c189f854a2ae4a6125424b5a5346408d Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Fri, 26 Sep 2025 14:40:43 -0700 Subject: [PATCH 325/371] More stale bot refinement (#2312) --- .github/workflows/issue-management-stale-action.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index d3bc49d51..0727d6a69 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -33,8 +33,8 @@ jobs: - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: - days-before-stale: -1 - days-before-close: 365 + days-before-stale: 365 + days-before-close: 0 close-issue-label: stale close-issue-message: > Since there has been no activity on this enhancement for the past year we are closing it to help maintain our backlog. @@ -42,6 +42,7 @@ jobs: days-before-pr-stale: -1 days-before-pr-close: -1 only-labels: "enhancement" + exempt-issue-labels: "stale" # so that it won't close issues labeled as stale by "needs author feedback" - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: From b8204bd08bbbef834b8cc2de68c8664f31e5878e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 07:42:27 +0300 Subject: [PATCH 326/371] chore(deps): update github/codeql-action action to v3.30.5 (#2313) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e45343149..abf3910c2 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 - name: Initialize CodeQL - uses: github/codeql-action/init@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 311c37958..14e46f422 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@303c0aef88fc2fe5ff6d63d3b1596bfd83dfa1f9 # v3.30.4 + uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 with: sarif_file: results.sarif From 43d01372ae014ca054145e00e752ce95bda4ce01 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 07:42:49 +0300 Subject: [PATCH 327/371] chore(deps): update plugin com.gradleup.shadow to v9.2.2 (#2314) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index d3b0d433d..ef91fcb81 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,6 +1,6 @@ pluginManagement { plugins { - id("com.gradleup.shadow") version "9.1.0" + id("com.gradleup.shadow") version "9.2.2" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" id("com.gradle.develocity") version "4.2" } From 810610197d4d8c70a427038f042ae4846006ce5d Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 04:43:51 +0000 Subject: [PATCH 328/371] fix(deps): update dependency org.bouncycastle:bcprov-jdk18on to v1.82 (#2315) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- jmx-scraper/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jmx-scraper/build.gradle.kts b/jmx-scraper/build.gradle.kts index 23087eec0..e2f4dc461 100644 --- a/jmx-scraper/build.gradle.kts +++ b/jmx-scraper/build.gradle.kts @@ -42,7 +42,7 @@ testing { implementation("com.linecorp.armeria:armeria-junit5") implementation("com.linecorp.armeria:armeria-grpc") implementation("io.opentelemetry.proto:opentelemetry-proto:1.8.0-alpha") - implementation("org.bouncycastle:bcprov-jdk18on:1.81") + implementation("org.bouncycastle:bcprov-jdk18on:1.82") implementation("org.bouncycastle:bcpkix-jdk18on:1.82") } } From 2c41e66c48fa5015ee96e00f8ef7ecd6c925bdf8 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 04:44:17 +0000 Subject: [PATCH 329/371] fix(deps): update spotless packages to v8 (major) (#2316) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index aa1859d59..b86ae18a7 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -1,7 +1,7 @@ plugins { `kotlin-dsl` // When updating, update below in dependencies too - id("com.diffplug.spotless") version "7.2.1" + id("com.diffplug.spotless") version "8.0.0" } repositories { @@ -12,7 +12,7 @@ repositories { dependencies { // When updating, update above in plugins too - implementation("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:7.2.1") + implementation("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:8.0.0") implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.3.0") implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.6") From 2d572244350bb1760a87511c4ff59ea8f52b58e4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 29 Sep 2025 20:19:31 -0700 Subject: [PATCH 330/371] chore(deps): update gradle/actions action to v4.4.4 (#2317) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/build-common.yml | 8 ++++---- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index f4e171bb0..5e010d8f5 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -25,7 +25,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 with: cache-read-only: true diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index bb50858cc..21967e66c 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -30,7 +30,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -49,7 +49,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -105,7 +105,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -134,7 +134,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 with: cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index badd5f855..cdfa7fc54 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - name: Build and publish snapshots run: ./gradlew assemble publishToSonatype diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index abf3910c2..103401875 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: - name: Set up gradle if: matrix.language == 'java' - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - name: Initialize CodeQL uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index 8638dc7ee..eb3c4332e 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -28,7 +28,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - name: Build project and download dependencies run: ./gradlew build -x test diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 6fbd70463..9313d58ee 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: gradle/actions/wrapper-validation@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + - uses: gradle/actions/wrapper-validation@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index 1494926ba..dd5af9c21 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -27,7 +27,7 @@ jobs: run: | sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - run: ./gradlew dependencyCheckAnalyze env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3bbad7990..b63738cd1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - name: Build and publish artifacts if: ${{ !inputs.already-published }} @@ -224,7 +224,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@ed408507eac070d1f99cc633dbcf757c94c7933a # v4.4.3 + uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - name: Update apidiff baseline env: From b18407bb66179e60d60e69d6d6efa22207971134 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Tue, 30 Sep 2025 11:58:08 -0400 Subject: [PATCH 331/371] Update sampler readme (#2319) --- samplers/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samplers/README.md b/samplers/README.md index aee9dee5f..fcd21df10 100644 --- a/samplers/README.md +++ b/samplers/README.md @@ -12,7 +12,7 @@ To use: * Follow the [instructions](https://github.com/open-telemetry/opentelemetry-java/blob/main/sdk-extensions/incubator/README.md#declarative-configuration) to configure OpenTelemetry with declarative configuration. * Configure the `.tracer_provider.sampler` to include the `rule_based_routing` sampler. -NOTE: Not yet available for use with the OTEL java agent, but should be in the near future. Please check back for updates. +Support is now available for the java agent, see an [example here](https://github.com/open-telemetry/opentelemetry-java-examples/blob/main/javaagent). Schema for `rule_based_routing` sampler: From 70b7dc128d832f39cb5539d6037c547cd998b7e8 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 30 Sep 2025 22:54:20 +0200 Subject: [PATCH 332/371] Use mise to check for modified files (#2318) Co-authored-by: Trask Stalnaker --- .github/workflows/reusable-link-check.yml | 35 ++---------------- .mise/tasks/lint/.shellcheckrc | 3 ++ .mise/tasks/lint/links-in-modified-files.sh | 39 +++++++++++++++++++++ .mise/tasks/lint/links.sh | 8 +++++ .mise/tasks/lint/local-links.sh | 8 +++++ mise.toml | 12 +++---- 6 files changed, 66 insertions(+), 39 deletions(-) create mode 100644 .mise/tasks/lint/.shellcheckrc create mode 100755 .mise/tasks/lint/links-in-modified-files.sh create mode 100755 .mise/tasks/lint/links.sh create mode 100755 .mise/tasks/lint/local-links.sh diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index e891e6d06..87711b6ce 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -20,40 +20,9 @@ jobs: if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} - run: mise run lint-local-links - - - name: Get modified files - if: github.event_name == 'pull_request' - id: modified-files - run: | - merge_base=$(git merge-base origin/${{ github.base_ref }} HEAD) - # Using lychee's default extension filter here to match when it runs against all files - # Note: --diff-filter=d filters out deleted files - modified_files=$(git diff --name-only --diff-filter=d $merge_base...${{ github.event.pull_request.head.sha }} \ - | grep -E '\.(md|mkd|mdx|mdown|mdwn|mkdn|mkdown|markdown|html|htm|txt)$' \ - | tr '\n' ' ' || true) - echo "files=$modified_files" >> $GITHUB_OUTPUT - echo "Modified files: $modified_files" - - - name: Check if lychee config was modified - if: github.event_name == 'pull_request' - id: config-check - run: | - merge_base=$(git merge-base origin/${{ github.base_ref }} HEAD) - config_modified=$(git diff --name-only $merge_base...${{ github.event.pull_request.head.sha }} \ - | grep -E '^(\.github/config/lychee\.toml|mise\.toml)$' || true) - if [ -n "$config_modified" ]; then - echo "modified=true" >> $GITHUB_OUTPUT - fi + run: mise run lint:local-links - name: Link check (modified files only) - if: github.event_name == 'pull_request' && steps.modified-files.outputs.files != '' && steps.config-check.outputs.modified != 'true' - env: - GITHUB_TOKEN: ${{ github.token }} - run: mise run lint-links ${{ steps.modified-files.outputs.files }} - - - name: Link check (all files) - if: github.event_name != 'pull_request' || steps.config-check.outputs.modified == 'true' env: GITHUB_TOKEN: ${{ github.token }} - run: mise run lint-links + run: mise run lint:links-in-modified-files --base origin/${{ github.base_ref }} --head ${{ github.event.pull_request.head.sha }} --event ${{ github.event_name }} diff --git a/.mise/tasks/lint/.shellcheckrc b/.mise/tasks/lint/.shellcheckrc new file mode 100644 index 000000000..c186fb835 --- /dev/null +++ b/.mise/tasks/lint/.shellcheckrc @@ -0,0 +1,3 @@ +# shellcheck configuration for mise tasks +# SC2154: usage_* variables are set by mise framework +disable=SC2154 diff --git a/.mise/tasks/lint/links-in-modified-files.sh b/.mise/tasks/lint/links-in-modified-files.sh new file mode 100755 index 000000000..893723a44 --- /dev/null +++ b/.mise/tasks/lint/links-in-modified-files.sh @@ -0,0 +1,39 @@ +#!/usr/bin/env bash +#MISE description="Lint links in modified files" + +set -e + +#USAGE flag "--base " help="base branch to compare against" default="origin/main" +#USAGE flag "--head " help="head branch to compare against" default="" +#USAGE flag "--event " help="PR name" default="pull_request" + +if [ "$usage_head" == "''" ]; then + usage_head="" +fi + +# Check if lychee config was modified +config_modified=$(git diff --name-only --merge-base "$usage_base" "$usage_head" \ + | grep -E '^(\.github/config/lychee\.toml|.mise/tasks/lint|mise\.toml)$' || true) + +if [ -n "$config_modified" ] ; then + echo "config changes, checking all files." + mise run lint:links +elif [ "$usage_event" != "pull_request" ] ; then + echo "Not a PR - skipping link linting." + exit 0 +else + # Using lychee's default extension filter here to match when it runs against all files + # Note: --diff-filter=d filters out deleted files + modified_files=$(git diff --name-only --diff-filter=d "$usage_base" "$usage_head" \ + | grep -E '\.(md|mkd|mdx|mdown|mdwn|mkdn|mkdown|markdown|html|htm|txt)$' \ + | tr '\n' ' ' || true) + + if [ -z "$modified_files" ]; then + echo "No modified files, skipping link linting." + exit 0 + fi + + # shellcheck disable=SC2086 + mise run lint:links $modified_files +fi + diff --git a/.mise/tasks/lint/links.sh b/.mise/tasks/lint/links.sh new file mode 100755 index 000000000..479549f74 --- /dev/null +++ b/.mise/tasks/lint/links.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +#MISE description="Lint links in all files" + +set -e + +#USAGE arg "" var=#true help="files to check" default="." + +lychee --verbose --config .github/config/lychee.toml "$usage_file" diff --git a/.mise/tasks/lint/local-links.sh b/.mise/tasks/lint/local-links.sh new file mode 100755 index 000000000..e79694d95 --- /dev/null +++ b/.mise/tasks/lint/local-links.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +#MISE description="Lint links in all files" + +set -e + +#USAGE arg "" var=#true help="files to check" default="." + +lychee --verbose --scheme file --include-fragments "$usage_file" diff --git a/mise.toml b/mise.toml index bc7b9444a..0e27c5494 100644 --- a/mise.toml +++ b/mise.toml @@ -1,12 +1,12 @@ [tools] lychee = "0.20.1" -[tasks.lint-local-links] -run = 'lychee --verbose --scheme file --include-fragments {{arg(name="files", default=".")}}' - -[tasks.lint-links] -run = 'lychee --verbose --config .github/config/lychee.toml {{arg(name="files", var=true, default=".")}}' - [settings] # Only install tools explicitly defined in the [tools] section above idiomatic_version_file_enable_tools = [] + +# Windows configuration for file-based tasks +# Based on: https://github.com/jdx/mise/discussions/4461 +windows_executable_extensions = ["sh"] +windows_default_file_shell_args = "bash" +use_file_shell_for_executable_tasks = true From d82574708d53b5ea36b865f6cbd946e7dd7176ca Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 30 Sep 2025 21:52:08 +0000 Subject: [PATCH 333/371] chore(deps): update dependency java to v25 (#2278) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/build-common.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 21967e66c..a555b20a5 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -86,7 +86,7 @@ jobs: - 11 - 17 - 21 - - 24 # renovate: datasource=java-version + - 25 # renovate: datasource=java-version steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 From 222e4d0ad2701166c59a882c345f74e43a67cb92 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 30 Sep 2025 14:53:48 -0700 Subject: [PATCH 334/371] Update Renovate config to ignore JUnit 6 which requires Java 17+ (#2321) --- .github/renovate.json5 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 82f86cdf7..011c5cdd4 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -68,6 +68,16 @@ ], enabled: false, }, + { + // junit 6+ requires Java 17+ + matchPackageNames: [ + 'org.junit:**', + ], + matchUpdateTypes: [ + 'major', + ], + enabled: false, + }, { // junit-pioneer 2+ requires Java 11+ matchPackageNames: [ From e1549eea2b0c490b8119bc76bc354fe54ae4c465 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 30 Sep 2025 15:30:56 -0700 Subject: [PATCH 335/371] Fix stale automation (#2320) --- .github/workflows/issue-management-stale-action.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index 0727d6a69..bf5cfbf06 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -42,7 +42,9 @@ jobs: days-before-pr-stale: -1 days-before-pr-close: -1 only-labels: "enhancement" - exempt-issue-labels: "stale" # so that it won't close issues labeled as stale by "needs author feedback" + # "stale" exemption: so that it won't close issues labeled as stale by "needs author feedback" + # "needs author feedback" exemption: so that it won't remove the stale label added by the first action + exempt-issue-labels: "stale,needs author feedback" - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: From 36bb580d0ce10b475728287030844bf345b9100f Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 1 Oct 2025 08:05:06 +0300 Subject: [PATCH 336/371] chore(deps): update ossf/scorecard-action action to v2.4.3 (#2322) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/ossf-scorecard.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index 14e46f422..ee7954794 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -32,7 +32,7 @@ jobs: app-id: ${{ vars.OSSF_SCORECARD_APP_ID }} private-key: ${{ secrets.OSSF_SCORECARD_PRIVATE_KEY }} - - uses: ossf/scorecard-action@05b42c624433fc40578a4040d5cf5e36ddca8cde # v2.4.2 + - uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 with: repo_token: ${{ steps.create-token.outputs.token }} results_file: results.sarif From c3333882b116953d875a9ce2e872daa2bf78bc89 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Wed, 1 Oct 2025 12:12:24 -0700 Subject: [PATCH 337/371] Update Renovate config to ignore JUnit 6 which requires Java 17+ (take 2) (#2323) --- .github/renovate.json5 | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index 011c5cdd4..a7d6e7672 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -72,6 +72,7 @@ // junit 6+ requires Java 17+ matchPackageNames: [ 'org.junit:**', + 'org.junit.jupiter:**', ], matchUpdateTypes: [ 'major', From 143709a5436c78c11b67405fc04b67850da103a7 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 2 Oct 2025 08:38:01 -0700 Subject: [PATCH 338/371] Modernize/simplify JUnit Gradle configuration (#2324) --- ibm-mq-metrics/build.gradle.kts | 57 ++++++++++++--------------------- 1 file changed, 21 insertions(+), 36 deletions(-) diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index d57b87795..31e191841 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -9,20 +9,6 @@ description = "IBM-MQ metrics" otelJava.moduleName.set("io.opentelemetry.contrib.ibm-mq-metrics") application.mainClass.set("io.opentelemetry.ibm.mq.opentelemetry.Main") -sourceSets { - create("integrationTest") { - compileClasspath += sourceSets.main.get().output - runtimeClasspath += sourceSets.main.get().output - } -} - -val integrationTestImplementation by configurations.getting { - extendsFrom(configurations.implementation.get()) -} -val integrationTestRuntimeOnly by configurations.getting - -configurations["integrationTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get()) - val ibmClientJar: Configuration by configurations.creating { isCanBeResolved = true isCanBeConsumed = false @@ -42,13 +28,6 @@ dependencies { implementation("org.slf4j:slf4j-simple:2.0.17") testImplementation("com.google.guava:guava") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("org.assertj:assertj-core:3.27.6") - integrationTestImplementation("org.junit.jupiter:junit-jupiter-api:5.13.4") - integrationTestImplementation("io.opentelemetry:opentelemetry-sdk-testing") - integrationTestImplementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.1") - integrationTestImplementation("jakarta.jms:jakarta.jms-api:3.1.0") - integrationTestImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.4") - integrationTestRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.4") ibmClientJar("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1") { artifact { name = "com.ibm.mq.allclient" @@ -58,23 +37,29 @@ dependencies { } } -tasks.shadowJar { - dependencies { - exclude(dependency("com.ibm.mq:com.ibm.mq.allclient:9.4.3.1")) +testing { + suites { + val integrationTest by registering(JvmTestSuite::class) { + dependencies { + implementation("org.assertj:assertj-core:3.27.6") + implementation("io.opentelemetry:opentelemetry-sdk-testing") + implementation("com.ibm.mq:com.ibm.mq.jakarta.client:9.4.3.1") + implementation("jakarta.jms:jakarta.jms-api:3.1.0") + } + + targets { + all { + testTask.configure { + shouldRunAfter(tasks.test) + } + } + } + } } } -val integrationTest = tasks.register("integrationTest") { - description = "Runs integration tests." - group = "verification" - - testClassesDirs = sourceSets["integrationTest"].output.classesDirs - classpath = sourceSets["integrationTest"].runtimeClasspath - shouldRunAfter("test") - - useJUnitPlatform() - - testLogging { - events("passed") +tasks.shadowJar { + dependencies { + exclude(dependency("com.ibm.mq:com.ibm.mq.allclient")) } } From 0aca7d7b3d5a8714dd276bc40bea8787849bd30f Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 2 Oct 2025 17:30:55 -0700 Subject: [PATCH 339/371] Make stale issue automation jobs mutually exclusive (#2326) --- .../issue-management-stale-action.yml | 23 +++++++++++++------ 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index bf5cfbf06..ca67f5ef6 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -17,11 +17,14 @@ jobs: pull-requests: write # for actions/stale to close stale PRs runs-on: ubuntu-latest steps: + # Action #1: Handle issues/PRs awaiting author feedback + # - After 7 days inactive: Adds "stale" label + warning comment + # - After 7 more days inactive: Closes - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: + only-labels: "needs author feedback" days-before-stale: 7 days-before-close: 7 - only-labels: "needs author feedback" stale-issue-label: stale stale-issue-message: > This issue has been labeled as stale due to lack of activity and needing author feedback. @@ -31,21 +34,27 @@ jobs: This PR has been labeled as stale due to lack of activity and needing author feedback. It will be automatically closed if there is no further activity over the next 7 days. + # Action #2: Close old enhancement requests + # - Targets: Issues with "enhancement" label (but NOT "needs author feedback") + # - After 365 days inactive: Adds "stale" label + closes immediately (no warning period) + # - Skips: Issues with "needs author feedback" to avoid conflicts with Action #1 - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: + only-labels: "enhancement" + # Skip issues that need author feedback (handled by the first action with 7+7 day policy) + exempt-issue-labels: "needs author feedback" + days-before-pr-stale: -1 + days-before-pr-close: -1 days-before-stale: 365 days-before-close: 0 close-issue-label: stale close-issue-message: > Since there has been no activity on this enhancement for the past year we are closing it to help maintain our backlog. Anyone who would like to work on it is still welcome to do so, and we can re-open it at that time. - days-before-pr-stale: -1 - days-before-pr-close: -1 - only-labels: "enhancement" - # "stale" exemption: so that it won't close issues labeled as stale by "needs author feedback" - # "needs author feedback" exemption: so that it won't remove the stale label added by the first action - exempt-issue-labels: "stale,needs author feedback" + # Action #3: Handle stale PRs + # - After 180 days inactive: Adds "stale" label + warning comment + # - After 14 more days inactive: Closes - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: days-before-issue-stale: -1 From 2777b07fc3cee0988a0ab2a5b4faccc581357dc9 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 17:59:54 -0700 Subject: [PATCH 340/371] fix(deps): update develocity packages to v4.2.1 (#2328) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index b86ae18a7..e963b8e8e 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.6") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") - implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2") + implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2.1") implementation("me.champeau.gradle.japicmp:me.champeau.gradle.japicmp.gradle.plugin:0.4.6") implementation("com.google.auto.value:auto-value-annotations:1.11.0") } diff --git a/settings.gradle.kts b/settings.gradle.kts index ef91fcb81..1df4d33ad 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.gradleup.shadow") version "9.2.2" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.2" + id("com.gradle.develocity") version "4.2.1" } } From 348a525631955aeba175123c6bfd00994c2e5a39 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 2 Oct 2025 18:00:16 -0700 Subject: [PATCH 341/371] fix(deps): update all patch versions (#2327) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- compressors/compressor-zstd/build.gradle.kts | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 103401875..94fcf732f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 - name: Initialize CodeQL - uses: github/codeql-action/init@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index ee7954794..a4054ef1b 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@3599b3baa15b485a2e49ef411a7a4bb2452e7f93 # v3.30.5 + uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 with: sarif_file: results.sarif diff --git a/compressors/compressor-zstd/build.gradle.kts b/compressors/compressor-zstd/build.gradle.kts index 46a72759b..acc7f3fe9 100644 --- a/compressors/compressor-zstd/build.gradle.kts +++ b/compressors/compressor-zstd/build.gradle.kts @@ -9,7 +9,7 @@ otelJava.moduleName.set("io.opentelemetry.contrib.compressor.zstd") dependencies { api("io.opentelemetry:opentelemetry-exporter-common") - implementation("com.github.luben:zstd-jni:1.5.7-4") + implementation("com.github.luben:zstd-jni:1.5.7-5") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-exporter-otlp") From 855df7f322012a3c5229b0b9cdcff7b1715e6776 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Sat, 4 Oct 2025 06:10:03 +0200 Subject: [PATCH 342/371] add end to end test for declarative config for aws (#2047) --- aws-resources/build.gradle.kts | 13 ++++++++ .../ResourceComponentProviderTest.java | 28 +++++++++++------ .../test/resources/declarative-config.yaml | 10 ++++++ azure-resources/build.gradle.kts | 12 +++++++ .../ResourceComponentProviderTest.java | 27 +++++++++++----- .../test/resources/declarative-config.yaml | 10 ++++++ cloudfoundry-resources/build.gradle.kts | 31 +++++++++++++++++++ .../ResourceComponentProviderTest.java | 25 +++++++++------ .../test/resources/declarative-config.yaml | 10 ++++++ maven-extension/build.gradle.kts | 3 ++ .../ResourceComponentProviderTest.java | 19 ++++++++++++ .../test/resources/declarative-config.yaml | 10 ++++++ 12 files changed, 172 insertions(+), 26 deletions(-) create mode 100644 aws-resources/src/test/resources/declarative-config.yaml create mode 100644 azure-resources/src/test/resources/declarative-config.yaml create mode 100644 cloudfoundry-resources/src/test/resources/declarative-config.yaml create mode 100644 maven-extension/src/test/resources/declarative-config.yaml diff --git a/aws-resources/build.gradle.kts b/aws-resources/build.gradle.kts index 580ecb44f..8c56b17df 100644 --- a/aws-resources/build.gradle.kts +++ b/aws-resources/build.gradle.kts @@ -21,11 +21,24 @@ dependencies { implementation("com.squareup.okhttp3:okhttp") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + testImplementation("io.opentelemetry:opentelemetry-exporter-logging") testImplementation("com.linecorp.armeria:armeria-junit5") testRuntimeOnly("org.bouncycastle:bcpkix-jdk15on") testImplementation("com.google.guava:guava") testImplementation("org.skyscreamer:jsonassert") } + +tasks { + withType().configureEach { + environment( + "AWS_REGION" to "us-east-1", + "AWS_LAMBDA_FUNCTION_NAME" to "my-function", + "AWS_LAMBDA_FUNCTION_VERSION" to "1.2.3" + ) + jvmArgs("-Dotel.experimental.config.file=${project.projectDir.resolve("src/test/resources/declarative-config.yaml")}") + } +} diff --git a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java index 1534aca17..51e21854b 100644 --- a/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java +++ b/aws-resources/src/test/java/io/opentelemetry/contrib/aws/resource/ResourceComponentProviderTest.java @@ -7,18 +7,28 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.common.ComponentLoader; -import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.semconv.incubating.CloudIncubatingAttributes; +import org.assertj.core.api.InstanceOfAssertFactory; import org.junit.jupiter.api.Test; class ResourceComponentProviderTest { - @Test - @SuppressWarnings("rawtypes") - void providerIsLoaded() { - Iterable providers = - ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) - .load(ComponentProvider.class); - assertThat(providers).extracting(ComponentProvider::getName).containsExactly("aws"); + void endToEnd() { + assertThat( + AutoConfiguredOpenTelemetrySdk.builder() + .build() + .getOpenTelemetrySdk() + .getSdkTracerProvider()) + .extracting("sharedState") + .extracting("resource") + .extracting( + "attributes", + new InstanceOfAssertFactory<>(Attributes.class, OpenTelemetryAssertions::assertThat)) + .containsEntry( + CloudIncubatingAttributes.CLOUD_PROVIDER, + CloudIncubatingAttributes.CloudProviderIncubatingValues.AWS); } } diff --git a/aws-resources/src/test/resources/declarative-config.yaml b/aws-resources/src/test/resources/declarative-config.yaml new file mode 100644 index 000000000..da52af7d7 --- /dev/null +++ b/aws-resources/src/test/resources/declarative-config.yaml @@ -0,0 +1,10 @@ +file_format: "1.0-rc.1" +resource: + detection/development: + detectors: + - aws: +tracer_provider: + processors: + - simple: + exporter: + console: diff --git a/azure-resources/build.gradle.kts b/azure-resources/build.gradle.kts index 015d0511d..05c032d70 100644 --- a/azure-resources/build.gradle.kts +++ b/azure-resources/build.gradle.kts @@ -29,6 +29,8 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure") testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") + testImplementation("io.opentelemetry:opentelemetry-exporter-logging") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") testImplementation("com.google.guava:guava") @@ -36,3 +38,13 @@ dependencies { testImplementation("org.assertj:assertj-core") testImplementation("com.linecorp.armeria:armeria-junit5") } + +tasks { + withType().configureEach { + environment( + "WEBSITE_SITE_NAME" to "my-function", + "FUNCTIONS_EXTENSION_VERSION" to "1.2.3" + ) + jvmArgs("-Dotel.experimental.config.file=${project.projectDir.resolve("src/test/resources/declarative-config.yaml")}") + } +} diff --git a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java index f04e256ff..0f67f3919 100644 --- a/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java +++ b/azure-resources/src/test/java/io/opentelemetry/contrib/azure/resource/ResourceComponentProviderTest.java @@ -7,18 +7,29 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.common.ComponentLoader; -import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import io.opentelemetry.semconv.incubating.CloudIncubatingAttributes; +import org.assertj.core.api.InstanceOfAssertFactory; import org.junit.jupiter.api.Test; class ResourceComponentProviderTest { @Test - @SuppressWarnings("rawtypes") - void providerIsLoaded() { - Iterable providers = - ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) - .load(ComponentProvider.class); - assertThat(providers).extracting(ComponentProvider::getName).containsExactly("azure"); + void endToEnd() { + assertThat( + AutoConfiguredOpenTelemetrySdk.builder() + .build() + .getOpenTelemetrySdk() + .getSdkTracerProvider()) + .extracting("sharedState") + .extracting("resource") + .extracting( + "attributes", + new InstanceOfAssertFactory<>(Attributes.class, OpenTelemetryAssertions::assertThat)) + .containsEntry( + CloudIncubatingAttributes.CLOUD_PROVIDER, + CloudIncubatingAttributes.CloudProviderIncubatingValues.AZURE); } } diff --git a/azure-resources/src/test/resources/declarative-config.yaml b/azure-resources/src/test/resources/declarative-config.yaml new file mode 100644 index 000000000..748dbddd9 --- /dev/null +++ b/azure-resources/src/test/resources/declarative-config.yaml @@ -0,0 +1,10 @@ +file_format: "1.0-rc.1" +resource: + detection/development: + detectors: + - azure: +tracer_provider: + processors: + - simple: + exporter: + console: diff --git a/cloudfoundry-resources/build.gradle.kts b/cloudfoundry-resources/build.gradle.kts index 3324504b5..d70c44500 100644 --- a/cloudfoundry-resources/build.gradle.kts +++ b/cloudfoundry-resources/build.gradle.kts @@ -23,4 +23,35 @@ dependencies { testImplementation("io.opentelemetry:opentelemetry-api-incubator") testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + testImplementation("io.opentelemetry:opentelemetry-exporter-logging") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") +} + +tasks { + withType().configureEach { + environment( + "VCAP_APPLICATION" to """ + { + "application_id": "0193a038-e615-7e5e-92ca-f4bcd7ba0a25", + "application_name": "cf-app-name", + "application_uris": [ + "testapp.example.com" + ], + "cf_api": "https://api.cf.example.com", + "limits": { + "fds": 256 + }, + "instance_index": 1, + "organization_id": "0193a375-8d8e-7e0c-a832-01ce9ded40dc", + "organization_name": "cf-org-name", + "process_id": "0193a4e3-8fd3-71b9-9fe3-5640c53bf1e2", + "process_type": "web", + "space_id": "0193a7e7-da17-7ea4-8940-b1e07b401b16", + "space_name": "cf-space-name", + "users": null + } + """.trimIndent(), + ) + jvmArgs("-Dotel.experimental.config.file=${project.projectDir.resolve("src/test/resources/declarative-config.yaml")}") + } } diff --git a/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java b/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java index 2e1e434b2..b4b659156 100644 --- a/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java +++ b/cloudfoundry-resources/src/test/java/io/opentelemetry/contrib/cloudfoundry/resources/ResourceComponentProviderTest.java @@ -7,18 +7,25 @@ import static org.assertj.core.api.Assertions.assertThat; -import io.opentelemetry.common.ComponentLoader; -import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import org.assertj.core.api.InstanceOfAssertFactory; import org.junit.jupiter.api.Test; class ResourceComponentProviderTest { - @Test - @SuppressWarnings("rawtypes") - void providerIsLoaded() { - Iterable providers = - ComponentLoader.forClassLoader(ResourceComponentProviderTest.class.getClassLoader()) - .load(ComponentProvider.class); - assertThat(providers).extracting(ComponentProvider::getName).containsExactly("cloud_foundry"); + void endToEnd() { + assertThat( + AutoConfiguredOpenTelemetrySdk.builder() + .build() + .getOpenTelemetrySdk() + .getSdkTracerProvider()) + .extracting("sharedState") + .extracting("resource") + .extracting( + "attributes", + new InstanceOfAssertFactory<>(Attributes.class, OpenTelemetryAssertions::assertThat)) + .containsEntry("cloudfoundry.app.name", "cf-app-name"); } } diff --git a/cloudfoundry-resources/src/test/resources/declarative-config.yaml b/cloudfoundry-resources/src/test/resources/declarative-config.yaml new file mode 100644 index 000000000..dc6ddf5d3 --- /dev/null +++ b/cloudfoundry-resources/src/test/resources/declarative-config.yaml @@ -0,0 +1,10 @@ +file_format: "1.0-rc.1" +resource: + detection/development: + detectors: + - cloud_foundry: +tracer_provider: + processors: + - simple: + exporter: + console: diff --git a/maven-extension/build.gradle.kts b/maven-extension/build.gradle.kts index 15755f0ce..a100335ee 100644 --- a/maven-extension/build.gradle.kts +++ b/maven-extension/build.gradle.kts @@ -33,7 +33,10 @@ dependencies { compileOnly("org.apache.maven:maven-core:3.5.0") // do not auto-update, support older mvn versions compileOnly("org.slf4j:slf4j-api") + testImplementation("io.opentelemetry:opentelemetry-sdk-testing") testImplementation("io.opentelemetry:opentelemetry-api-incubator") + testImplementation("io.opentelemetry:opentelemetry-exporter-logging") + testImplementation("io.opentelemetry:opentelemetry-sdk-extension-incubator") testImplementation("org.apache.maven:maven-core:3.5.0") testImplementation("org.slf4j:slf4j-simple") } diff --git a/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java b/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java index e992f24da..940d8dd83 100644 --- a/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java +++ b/maven-extension/src/test/java/io/opentelemetry/maven/resources/ResourceComponentProviderTest.java @@ -7,8 +7,12 @@ import static org.assertj.core.api.Assertions.assertThat; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.common.ComponentLoader; +import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; +import io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions; +import org.assertj.core.api.InstanceOfAssertFactory; import org.junit.jupiter.api.Test; class ResourceComponentProviderTest { @@ -21,4 +25,19 @@ void providerIsLoaded() { .load(ComponentProvider.class); assertThat(providers).extracting(ComponentProvider::getName).contains("maven"); } + + @Test + void endToEnd() { + assertThat( + AutoConfiguredOpenTelemetrySdk.builder() + .build() + .getOpenTelemetrySdk() + .getSdkTracerProvider()) + .extracting("sharedState") + .extracting("resource") + .extracting( + "attributes", + new InstanceOfAssertFactory<>(Attributes.class, OpenTelemetryAssertions::assertThat)) + .containsEntry("telemetry.distro.name", "opentelemetry-maven-extension"); + } } diff --git a/maven-extension/src/test/resources/declarative-config.yaml b/maven-extension/src/test/resources/declarative-config.yaml new file mode 100644 index 000000000..666fefd36 --- /dev/null +++ b/maven-extension/src/test/resources/declarative-config.yaml @@ -0,0 +1,10 @@ +file_format: "1.0-rc.1" +resource: + detection/development: + detectors: + - maven: +tracer_provider: + processors: + - simple: + exporter: + console: From 10765d25131e44e21497e9c6e194e69ba60d993e Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 07:50:12 +0300 Subject: [PATCH 343/371] chore(deps): update gradle/actions action to v5 (#2332) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/auto-spotless-check.yml | 2 +- .github/workflows/build-common.yml | 8 ++++---- .github/workflows/build.yml | 2 +- .github/workflows/codeql.yml | 2 +- .github/workflows/copilot-setup-steps.yml | 2 +- .github/workflows/gradle-wrapper-validation.yml | 2 +- .github/workflows/owasp-dependency-check-daily.yml | 2 +- .github/workflows/release.yml | 4 ++-- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/auto-spotless-check.yml b/.github/workflows/auto-spotless-check.yml index 5e010d8f5..e5ec0e2e4 100644 --- a/.github/workflows/auto-spotless-check.yml +++ b/.github/workflows/auto-spotless-check.yml @@ -25,7 +25,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: cache-read-only: true diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index a555b20a5..20ea2f153 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -30,7 +30,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -49,7 +49,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -105,7 +105,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: cache-read-only: ${{ inputs.cache-read-only }} @@ -134,7 +134,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 with: cache-read-only: ${{ inputs.cache-read-only }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cdfa7fc54..c16ad6cd8 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -33,7 +33,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Build and publish snapshots run: ./gradlew assemble publishToSonatype diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 94fcf732f..3dbad303e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,7 +47,7 @@ jobs: - name: Set up gradle if: matrix.language == 'java' - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Initialize CodeQL uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml index eb3c4332e..e167d7c5e 100644 --- a/.github/workflows/copilot-setup-steps.yml +++ b/.github/workflows/copilot-setup-steps.yml @@ -28,7 +28,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Build project and download dependencies run: ./gradlew build -x test diff --git a/.github/workflows/gradle-wrapper-validation.yml b/.github/workflows/gradle-wrapper-validation.yml index 9313d58ee..d2e124e7d 100644 --- a/.github/workflows/gradle-wrapper-validation.yml +++ b/.github/workflows/gradle-wrapper-validation.yml @@ -17,4 +17,4 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - uses: gradle/actions/wrapper-validation@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/wrapper-validation@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 diff --git a/.github/workflows/owasp-dependency-check-daily.yml b/.github/workflows/owasp-dependency-check-daily.yml index dd5af9c21..c8ddc6b6c 100644 --- a/.github/workflows/owasp-dependency-check-daily.yml +++ b/.github/workflows/owasp-dependency-check-daily.yml @@ -27,7 +27,7 @@ jobs: run: | sed -i "s/org.gradle.jvmargs=/org.gradle.jvmargs=-Xmx3g /" gradle.properties - - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - run: ./gradlew dependencyCheckAnalyze env: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b63738cd1..3ef09e0a2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: java-version: 17 - name: Set up gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Build and publish artifacts if: ${{ !inputs.already-published }} @@ -224,7 +224,7 @@ jobs: java-version: 17 - name: Set up Gradle - uses: gradle/actions/setup-gradle@748248ddd2a24f49513d8f472f81c3a07d4d50e1 # v4.4.4 + uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Update apidiff baseline env: From 7a5935455e1750550ce9d7fe8e230000276688ba Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 07:50:37 +0300 Subject: [PATCH 344/371] fix(deps): update dependency org.junit:junit-bom to v5.14.0 (#2331) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index 8a72f5dc1..ffd6f2bf0 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -150,7 +150,7 @@ testing { dependencies { implementation(project(project.path)) - implementation(enforcedPlatform("org.junit:junit-bom:5.13.4")) + implementation(enforcedPlatform("org.junit:junit-bom:5.14.0")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) implementation(enforcedPlatform("com.google.guava:guava-bom:33.5.0-jre")) implementation(enforcedPlatform("com.linecorp.armeria:armeria-bom:1.33.4")) From a3117def1a4175e35451271b6969e6183885dd67 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 04:59:07 +0000 Subject: [PATCH 345/371] chore(deps): update actions/stale action to v10.1.0 (#2329) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/issue-management-stale-action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index ca67f5ef6..69682f6a1 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -20,7 +20,7 @@ jobs: # Action #1: Handle issues/PRs awaiting author feedback # - After 7 days inactive: Adds "stale" label + warning comment # - After 7 more days inactive: Closes - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: only-labels: "needs author feedback" days-before-stale: 7 @@ -38,7 +38,7 @@ jobs: # - Targets: Issues with "enhancement" label (but NOT "needs author feedback") # - After 365 days inactive: Adds "stale" label + closes immediately (no warning period) # - Skips: Issues with "needs author feedback" to avoid conflicts with Action #1 - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: only-labels: "enhancement" # Skip issues that need author feedback (handled by the first action with 7+7 day policy) @@ -55,7 +55,7 @@ jobs: # Action #3: Handle stale PRs # - After 180 days inactive: Adds "stale" label + warning comment # - After 14 more days inactive: Closes - - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 + - uses: actions/stale@5f858e3efba33a5ca4407a664cc011ad407f2008 # v10.1.0 with: days-before-issue-stale: -1 days-before-issue-close: -1 From 89b99af13d3abdef28ff71e695f42269eb22b748 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Oct 2025 10:12:04 -0700 Subject: [PATCH 346/371] chore(deps): update jdx/mise-action action to v3.3.1 (#2330) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/reusable-link-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 87711b6ce..efb8e2874 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -14,7 +14,7 @@ jobs: with: fetch-depth: 0 # needed for merge-base below - - uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3.2.0 + - uses: jdx/mise-action@e3d7b8d67a7958d1207f6ed871e83b1ea780e7b0 # v3.3.1 - name: Link check - relative links (all files) if: github.event_name == 'pull_request' From aa315a5c65ba040eb7edb687742141dc005b5995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Wed, 8 Oct 2025 00:37:42 +0200 Subject: [PATCH 347/371] Disk buffering api implementation (#2183) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> Co-authored-by: jason plumb <75337021+breedx-splk@users.noreply.github.com> --- disk-buffering/DESIGN.md | 69 ++-- disk-buffering/README.md | 173 ++++---- disk-buffering/assets/reading-flow.png | Bin 120001 -> 54801 bytes disk-buffering/assets/writing-flow.png | Bin 74418 -> 69466 bytes .../buffering/LogRecordFromDiskExporter.java | 44 -- .../buffering/LogRecordToDiskExporter.java | 65 --- .../buffering/MetricFromDiskExporter.java | 44 -- .../disk/buffering/MetricToDiskExporter.java | 76 ---- .../contrib/disk/buffering/SignalType.java | 12 - .../disk/buffering/SpanFromDiskExporter.java | 44 -- .../disk/buffering/SpanToDiskExporter.java | 65 --- .../buffering/exporters/ExporterCallback.java | 38 -- .../exporters/LogRecordToDiskExporter.java | 81 ++++ .../exporters/MetricToDiskExporter.java | 99 +++++ .../exporters/NoopExporterCallback.java | 24 -- .../exporters/SpanToDiskExporter.java | 22 +- .../exporters/callback/ExporterCallback.java | 30 ++ .../callback/NoopExporterCallback.java | 21 + .../internal/exporter/FromDiskExporter.java | 15 - .../exporter/FromDiskExporterBuilder.java | 55 --- .../exporter/FromDiskExporterImpl.java | 86 ---- .../internal/exporter/NoopSerializer.java | 30 -- .../internal/exporter/ToDiskExporter.java | 66 --- .../exporter/ToDiskExporterBuilder.java | 47 --- .../exporters/SignalStorageExporter.java | 18 +- .../LogRecordDataDeserializer.java | 6 - .../deserializers/MetricDataDeserializer.java | 6 - .../deserializers/SignalDeserializer.java | 5 - .../deserializers/SpanDataDeserializer.java | 6 - .../internal/storage/FileSignalStorage.java | 92 +++++ .../internal/storage/FileSpanStorage.java | 39 -- .../internal/storage/FolderManager.java | 52 ++- .../buffering/internal/storage/Storage.java | 164 +++++--- .../internal/storage/StorageBuilder.java | 66 --- .../internal/storage/StorageIterator.java | 90 +++++ .../internal/storage/files/ReadableFile.java | 73 ++-- .../internal/storage/files/WritableFile.java | 6 +- .../reader/DelimitedProtoStreamReader.java | 14 +- .../storage/files/reader/ProcessResult.java | 13 - .../storage/files/reader/ReadResult.java | 15 - .../storage/files/reader/StreamReader.java | 2 +- .../storage/responses/ReadableResult.java | 14 +- .../buffering/internal/utils/DebugLogger.java | 41 -- .../buffering/internal/utils/SignalTypes.java | 12 - .../storage/impl/FileLogRecordStorage.java | 63 +++ .../storage/impl/FileMetricStorage.java | 64 +++ .../storage/impl/FileSpanStorage.java | 63 +++ .../impl/FileStorageConfiguration.java} | 36 +- .../buffering/storage/result/WriteResult.java | 8 +- .../buffering/FromDiskExporterImplTest.java | 114 ------ .../disk/buffering/IntegrationTest.java | 215 +++++----- .../LogRecordToDiskExporterTest.java | 64 --- .../buffering/MetricToDiskExporterTest.java | 77 ---- .../buffering/SpanFromDiskExporterTest.java | 145 ------- .../buffering/SpanToDiskExporterTest.java | 63 --- .../internal/exporter/ToDiskExporterTest.java | 90 ----- .../exporters/SignalStorageExporterTest.java | 40 +- .../internal/storage/FolderManagerTest.java | 15 +- .../internal/storage/StorageTest.java | 380 ++++++++---------- .../buffering/internal/storage/TestData.java | 78 +++- .../storage/files/ReadableFileTest.java | 163 +------- .../storage/files/WritableFileTest.java | 2 +- .../testutils/BaseSignalSerializerTest.java | 2 +- 63 files changed, 1357 insertions(+), 2265 deletions(-) delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/ExporterCallback.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/NoopExporterCallback.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java rename disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/{ => internal}/exporters/SignalStorageExporter.java (76%) create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSignalStorage.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageIterator.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ProcessResult.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java delete mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/SignalTypes.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java create mode 100644 disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java rename disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/{config/StorageConfiguration.java => storage/impl/FileStorageConfiguration.java} (72%) delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporterTest.java delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporterTest.java delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporterTest.java delete mode 100644 disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java rename disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/{ => internal}/exporters/SignalStorageExporterTest.java (78%) diff --git a/disk-buffering/DESIGN.md b/disk-buffering/DESIGN.md index 01f6048da..3bd1e3f01 100644 --- a/disk-buffering/DESIGN.md +++ b/disk-buffering/DESIGN.md @@ -1,59 +1,62 @@ # Design Overview -There are three main disk-writing exporters provided by this module: +The core of disk buffering +is [SignalStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java). +SignalStorage is an abstraction that defines the bare minimum functionalities needed for +implementations to allow writing and reading signals. -* [LogRecordToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java) -* [MetricToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java) -* [SpanToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java)) +There is a default implementation per signal that writes serialized signal items to protobuf +delimited messages into files, where each file's name represents a timestamp of when it was created, +which will help later to know when it's ready to read, as well as when it's expired. These +implementations are the following: -Each is responsible for writing a specific type of telemetry to disk storage for later -harvest/ingest. +* [FileSpanStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java) +* [FileLogRecordStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java) +* [FileMetricStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java) -For later reading, there are: +Each one has a `create()` method that takes a destination directory (to store data into) and an +optional [FileStorageConfiguration](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileStorageConfiguration.java) +to have a finer control of the storing behavior. -* [LogRecordFromToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java) -* [MetricFromDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java) -* [SpanFromDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java)) +Even +though [SignalStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java) +can receive signal items directly to be stored in disk, there are convenience exporter +implementations for each signal that handle the storing process on your behalf. Those are the +following: -Each one of those has a `create()` method that takes a delegate exporter (to send data -to ingest) and the `StorageConfiguration` that tells them where to find buffered data. +* [SpanToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java) +* [LogRecordToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java) +* [MetricToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java) -As explained in the [README](README.md), this has to be triggered manually by the consumer of -this library and does not happen automatically. +Each receive their +respective [SignalStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java) +object to delegate signals to as well as an optional callback object to notify its operations. ## Writing overview ![Writing flow](assets/writing-flow.png) -* The writing process happens automatically within its `export(Collection signals)` - method, which is called by the configured signal processor. -* When a set of signals is received, these are delegated over to - a type-specific wrapper of [ToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java) - class which then serializes them using an implementation - of [SignalSerializer](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/serializers/SignalSerializer.java) - and then the serialized data is appended into a File using an instance of - the [Storage](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java) - class. +* Via the convenience toDisk exporters, the writing process happens automatically within their + `export(Collection signals)` method, which is called by the configured signal + processor. +* When a set of signals is received, these are delegated over to a type-specific serializer + and then the serialized data is appended into a file. * The data is written into a file directly, without the use of a buffer, to make sure no data gets lost in case the application ends unexpectedly. -* Each disk exporter stores its signals in its own folder, which is expected to contain files +* Each signal storage stores its signals in its own folder, which is expected to contain files that belong to that type of signal only. * Each file may contain more than a batch of signals if the configuration parameters allow enough limit size for it. * If the configured folder size for the signals has been reached and a new file is needed to be created to keep storing new data, the oldest available file will be removed to make space for the new one. -* The [Storage](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java), - [FolderManager](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java) - and [WritableFile](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java) - files contain more information on the details of the writing process into a file. ## Reading overview ![Reading flow](assets/reading-flow.png) -* The reading process has to be triggered manually by the library consumer as explained in - the [README](README.md). +* The reading process has to be triggered manually by the library consumer via the signal storage + iterator. * A single file is read at a time and updated to remove the data gathered from it after it is successfully exported, until it's emptied. Each file previously created during the writing process has a timestamp in milliseconds, which is used to determine what file to start @@ -62,9 +65,3 @@ this library and does not happen automatically. the time of creating the disk exporter, then it will be ignored, and the next oldest (and unexpired) one will be used instead. * All the stale and empty files will be removed as a new file is created. -* The [Storage](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java), - [FolderManager](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java) - and [ReadableFile](src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java) - files contain more information on the details of the file reading process. -* Note that the reader delegates the data to the exporter exactly in the way it has received the - data - it does not try to batch data (but this could be an optimization in the future). diff --git a/disk-buffering/README.md b/disk-buffering/README.md index 72e17144d..9178794ad 100644 --- a/disk-buffering/README.md +++ b/disk-buffering/README.md @@ -1,109 +1,132 @@ # Disk buffering -This module provides exporters that store telemetry data in files which can be -sent later on demand. A high level description of how it works is that there are two separate -processes in place, one for writing data in disk, and one for reading/exporting the previously -stored data. +This module provides an abstraction +named [SignalStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/SignalStorage.java), +as well as default implementations for each signal type that allow writing signals to disk and +reading them later. -* Each exporter stores the received data automatically in disk right after it's received from its - processor. -* The reading of the data back from disk and exporting process has to be done manually. At - the moment there's no automatic mechanism to do so. There's more information on how it can be - achieved, under [Reading data](#reading-data). +For a more detailed information on how the whole process works, take a look at +the [DESIGN.md](DESIGN.md) file. -> For a more detailed information on how the whole process works, take a look at -> the [DESIGN.md](DESIGN.md) file. +## Default implementation usage -## Configuration +The default implementations are the following: -The configurable parameters are provided **per exporter**, the available ones are: +* [FileSpanStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java) +* [FileLogRecordStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java) +* [FileMetricStorage](src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java) -* Max file size, defaults to 1MB. -* Max folder size, defaults to 10MB. All files are stored in a single folder per-signal, therefore - if all 3 types of signals are stored, the total amount of space from disk to be taken by default - would be of 30MB. -* Max age for file writing, defaults to 30 seconds. -* Min age for file reading, defaults to 33 seconds. It must be greater that the max age for file - writing. -* Max age for file reading, defaults to 18 hours. After that time passes, the file will be - considered stale and will be removed when new files are created. No more data will be read from a - file past this time. - -## Usage +### Set up -### Storing data +We need to create a signal storage object per signal type to start writing signals to disk. Each +`File*Storage` implementation has a `create()` function that receives: + +* A File directory to store the signal files. Note that each signal storage object must have a + dedicated directory to work properly. +* (Optional) a configuration object. -In order to use it, you need to wrap your own exporter with a new instance of -the ones provided in here: +The available configuration parameters are the following: -* For a LogRecordExporter, it must be wrapped within - a [LogRecordToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java). -* For a MetricExporter, it must be wrapped within - a [MetricToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java). -* For a SpanExporter, it must be wrapped within - a [SpanToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java). +* Max file size, defaults to 1MB. +* Max folder size, defaults to 10MB. +* Max age for file writing. It sets the time window where a file can get signals appended to it. + Defaults to 30 seconds. +* Min age for file reading. It sets the time to wait before starting to read from a file after + its creation. Defaults to 33 seconds. It must be greater that the max age for file writing. +* Max age for file reading. After that time passes, the file will be considered stale and will be + removed when new files are created. No more data will be read from a file past this time. Defaults + to 18 hours. -Each wrapper will need the following when instantiating them: +```java +// Root dir +File rootDir = new File("/some/root"); -* The exporter to be wrapped. -* A File instance of the root directory where all the data is going to be written. The same root dir - can be used for all the wrappers, since each will create their own folder inside it. -* An instance - of [StorageConfiguration](src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java) - with the desired parameters. You can create one with default values by - calling `StorageConfiguration.getDefault()`. +// Setting up span storage +SignalStorage.Span spanStorage = FileSpanStorage.create(new File(rootDir, "spans")); -After wrapping your exporters, you must register the wrapper as the exporter you'll use. It will -take care of always storing the data it receives. +// Setting up metric storage +SignalStorage.Metric metricStorage = FileMetricStorage.create(new File(rootDir, "metrics")); -#### Set up example for spans +// Setting up log storage +SignalStorage.LogRecord logStorage = FileLogRecordStorage.create(new File(rootDir, "logs")); +``` -### Writing data +### Storing data -The data is written in the disk by "ToDisk" exporters, these are exporters that serialize and store the data as received by their processors. If for some reason -the "ToDisk" cannot store data in the disk, they'll delegate the data to their wrapped exporter. +While you could manually call your `SignalStorage.write(items)` function, disk buffering +provides convenience exporters that you can use in your OpenTelemetry's instance, so +that all signals are automatically stored as they are created. -```java -// Creating the SpanExporter of our choice. -SpanExporter mySpanExporter = OtlpGrpcSpanExporter.getDefault(); +* For a span storage, use + a [SpanToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java). +* For a log storage, use + a [LogRecordToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java). +* For a metric storage, use + a [MetricToDiskExporter](src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java). -// Wrapping our exporter with its "ToDisk" exporter. -SpanToDiskExporter toDiskExporter = SpanToDiskExporter.create(mySpanExporter, StorageConfiguration.getDefault(new File("/my/signals/cache/dir"))); +Each will wrap a signal storage for its respective signal type, as well as an optional callback +to notify when it succeeds, fails, and gets shutdown. - // Registering the disk exporter within our OpenTelemetry instance. -SdkTracerProvider myTraceProvider = SdkTracerProvider.builder() - .addSpanProcessor(SimpleSpanProcessor.create(toDiskExporter)) +```java +// Setting up span to disk exporter +SpanToDiskExporter spanToDiskExporter = + SpanToDiskExporter.builder(spanStorage).setExporterCallback(spanCallback).build(); +// Setting up metric to disk +MetricToDiskExporter metricToDiskExporter = + MetricToDiskExporter.builder(metricStorage).setExporterCallback(metricCallback).build(); +// Setting up log to disk exporter +LogRecordToDiskExporter logToDiskExporter = + LogRecordToDiskExporter.builder(logStorage).setExporterCallback(logCallback).build(); + +// Using exporters in your OpenTelemetry instance. +OpenTelemetry openTelemetry = + OpenTelemetrySdk.builder() + // Using span to disk exporter + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(BatchSpanProcessor.builder(spanToDiskExporter).build()) + .build()) + // Using log to disk exporter + .setLoggerProvider( + SdkLoggerProvider.builder() + .addLogRecordProcessor( + BatchLogRecordProcessor.builder(logToDiskExporter).build()) + .build()) + // Using metric to disk exporter + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(PeriodicMetricReader.create(metricToDiskExporter)) + .build()) .build(); -OpenTelemetrySdk.builder() - .setTracerProvider(myTraceProvider) - .buildAndRegisterGlobal(); - ``` +Now when creating signals using your `OpenTelemetry` instance, those will get stored in disk. + ### Reading data -In order to read data, we need to create "FromDisk" exporters, which read data from the disk, parse it and delegate it -to their wrapped exporters. +In order to read data, we can iterate through our signal storage objects and then forward them to +a network exporter, as shown in the example for spans below. ```java -try { - SpanFromDiskExporter fromDiskExporter = SpanFromDiskExporter.create(memorySpanExporter, storageConfig); - if(fromDiskExporter.exportStoredBatch(1, TimeUnit.SECONDS)) { - // A batch was successfully exported and removed from disk. You can call this method for as long as it keeps returning true. - } else { - // Either there was no data in the disk or the wrapped exporter returned CompletableResultCode.ofFailure(). - } -} catch (IOException e) { - // Something unexpected happened. +// Example of reading an exporting spans from disk +OtlpHttpSpanExporter networkExporter; +Iterator> spanCollections = spanStorage.iterator(); +while(spanCollections.hasNext()){ + networkExporter.export(spanCollections.next()); } ``` +The `File*Storage` iterators delete the previously returned collection when `next()` is called, +assuming that if the next collection is requested is because the previous one was successfully +consumed. + Both the writing and reading processes can run in parallel and they don't overlap because each is supposed to happen in different files. We ensure that reader and writer don't -accidentally meet in the same file by using the configurable parameters. These parameters set non-overlapping time frames for each action to be done on a single file at a time. On top of that, there's a mechanism in -place to avoid overlapping on edge cases where the time frames ended but the resources haven't been -released. For that mechanism to work properly, this tool assumes that both the reading and the -writing actions are executed within the same application process. +accidentally meet in the same file by using the configurable parameters. These parameters set +non-overlapping time frames for each action to be done on a single file at a time. On top of that, +there's a mechanism in place to avoid overlapping on edge cases where the time frames ended but the +resources haven't been released. For that mechanism to work properly, this tool assumes that both +the reading and the writing actions are executed within the same application process. ## Component owners diff --git a/disk-buffering/assets/reading-flow.png b/disk-buffering/assets/reading-flow.png index 76b8de43871efa097dd283a0cce5e05eab1885f2..63750e5a366600bb0dc35eb4a0bfc8bfb50207f3 100644 GIT binary patch literal 54801 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Ê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≦rJDsZw;GZMHUUZzi>0p>7L!gz)Z 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 literal 120001 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 diff --git a/disk-buffering/assets/writing-flow.png b/disk-buffering/assets/writing-flow.png index c6144b301c6f77b42e68dc9e8f47495643117b97..b4b21359dc28d0d742da7dd515989c968c678a02 100644 GIT binary patch literal 69466 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 literal 74418 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 LL%vwn$p8NX#&kri diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java deleted file mode 100644 index 6c38be04a..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordFromDiskExporter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -public final class LogRecordFromDiskExporter implements FromDiskExporter { - - private final FromDiskExporterImpl delegate; - - public static LogRecordFromDiskExporter create(LogRecordExporter exporter, Storage storage) - throws IOException { - FromDiskExporterImpl delegate = - FromDiskExporterImpl.builder(storage) - .setDeserializer(SignalDeserializer.ofLogs()) - .setExportFunction(exporter::export) - .build(); - return new LogRecordFromDiskExporter(delegate); - } - - private LogRecordFromDiskExporter(FromDiskExporterImpl delegate) { - this.delegate = delegate; - } - - @Override - public boolean exportStoredBatch(long timeout, TimeUnit unit) throws IOException { - return delegate.exportStoredBatch(timeout, unit); - } - - @Override - public void shutdown() throws IOException { - delegate.shutdown(); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java deleted file mode 100644 index 1524723e8..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import io.opentelemetry.sdk.logs.export.LogRecordExporter; -import java.io.IOException; -import java.util.Collection; - -/** - * This class implements a {@link LogRecordExporter} that delegates to an instance of {@code - * ToDiskExporter}. - */ -public final class LogRecordToDiskExporter implements LogRecordExporter { - - private final ToDiskExporter delegate; - - /** - * Creates a new LogRecordToDiskExporter that will buffer LogRecordData telemetry on disk storage. - * - * @param delegate - The LogRecordExporter to delegate to if disk writing fails. - * @param storage - The Storage instance that specifies how storage is managed. - * @return A new LogRecordToDiskExporter instance. - */ - public static LogRecordToDiskExporter create(LogRecordExporter delegate, Storage storage) { - ToDiskExporter toDisk = - ToDiskExporter.builder(storage) - .setSerializer(SignalSerializer.ofLogs()) - .setExportFunction(delegate::export) - .build(); - return new LogRecordToDiskExporter(toDisk); - } - - // Visible for testing - LogRecordToDiskExporter(ToDiskExporter delegate) { - this.delegate = delegate; - } - - @Override - public CompletableResultCode export(Collection logs) { - return delegate.export(logs); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - try { - delegate.shutdown(); - return CompletableResultCode.ofSuccess(); - } catch (IOException e) { - return CompletableResultCode.ofFailure(); - } - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java deleted file mode 100644 index 36d478e7b..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricFromDiskExporter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -public final class MetricFromDiskExporter implements FromDiskExporter { - - private final FromDiskExporterImpl delegate; - - public static MetricFromDiskExporter create(MetricExporter exporter, Storage storage) - throws IOException { - FromDiskExporterImpl delegate = - FromDiskExporterImpl.builder(storage) - .setDeserializer(SignalDeserializer.ofMetrics()) - .setExportFunction(exporter::export) - .build(); - return new MetricFromDiskExporter(delegate); - } - - private MetricFromDiskExporter(FromDiskExporterImpl delegate) { - this.delegate = delegate; - } - - @Override - public boolean exportStoredBatch(long timeout, TimeUnit unit) throws IOException { - return delegate.exportStoredBatch(timeout, unit); - } - - @Override - public void shutdown() throws IOException { - delegate.shutdown(); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java deleted file mode 100644 index 2e0848684..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporter.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.metrics.InstrumentType; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.MetricData; -import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; -import io.opentelemetry.sdk.metrics.export.MetricExporter; -import java.io.IOException; -import java.util.Collection; - -/** - * This class implements a {@link MetricExporter} that delegates to an instance of {@code - * ToDiskExporter}. - */ -public final class MetricToDiskExporter implements MetricExporter { - - private final ToDiskExporter delegate; - private final AggregationTemporalitySelector aggregationTemporalitySelector; - - /** - * Creates a new MetricToDiskExporter that will buffer Metric telemetry on disk storage. - * - * @param delegate - The MetricExporter to delegate to if disk writing fails. - * @param storage - The Storage instance that specifies how storage is managed. - * @return A new MetricToDiskExporter instance. - */ - public static MetricToDiskExporter create(MetricExporter delegate, Storage storage) { - ToDiskExporter toDisk = - ToDiskExporter.builder(storage) - .setSerializer(SignalSerializer.ofMetrics()) - .setExportFunction(delegate::export) - .build(); - return new MetricToDiskExporter(toDisk, delegate); - } - - // VisibleForTesting - MetricToDiskExporter( - ToDiskExporter delegate, AggregationTemporalitySelector selector) { - this.delegate = delegate; - this.aggregationTemporalitySelector = selector; - } - - @Override - public CompletableResultCode export(Collection metrics) { - return delegate.export(metrics); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - try { - delegate.shutdown(); - } catch (IOException e) { - return CompletableResultCode.ofFailure(); - } - return CompletableResultCode.ofSuccess(); - } - - @Override - public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { - return aggregationTemporalitySelector.getAggregationTemporality(instrumentType); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java deleted file mode 100644 index c66ed940e..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SignalType.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -public enum SignalType { - SPAN, - LOG, - METRIC -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java deleted file mode 100644 index 9523c8a2f..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporter.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -public final class SpanFromDiskExporter implements FromDiskExporter { - - private final FromDiskExporterImpl delegate; - - public static SpanFromDiskExporter create(SpanExporter exporter, Storage storage) - throws IOException { - FromDiskExporterImpl delegate = - FromDiskExporterImpl.builder(storage) - .setDeserializer(SignalDeserializer.ofSpans()) - .setExportFunction(exporter::export) - .build(); - return new SpanFromDiskExporter(delegate); - } - - private SpanFromDiskExporter(FromDiskExporterImpl delegate) { - this.delegate = delegate; - } - - @Override - public boolean exportStoredBatch(long timeout, TimeUnit unit) throws IOException { - return delegate.exportStoredBatch(timeout, unit); - } - - @Override - public void shutdown() throws IOException { - delegate.shutdown(); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java deleted file mode 100644 index dcd79d3b0..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporter.java +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.IOException; -import java.util.Collection; - -/** - * This class implements a SpanExporter that delegates to an instance of {@code - * ToDiskExporter}. - */ -public final class SpanToDiskExporter implements SpanExporter { - - private final ToDiskExporter delegate; - - /** - * Creates a new SpanToDiskExporter that will buffer Span telemetry on disk storage. - * - * @param delegate - The SpanExporter to delegate to if disk writing fails. - * @param storage - The Storage instance that specifies how storage is managed. - * @return A new SpanToDiskExporter instance. - */ - public static SpanToDiskExporter create(SpanExporter delegate, Storage storage) { - ToDiskExporter toDisk = - ToDiskExporter.builder(storage) - .setSerializer(SignalSerializer.ofSpans()) - .setExportFunction(delegate::export) - .build(); - return new SpanToDiskExporter(toDisk); - } - - // Visible for testing - SpanToDiskExporter(ToDiskExporter delegate) { - this.delegate = delegate; - } - - @Override - public CompletableResultCode export(Collection spans) { - return delegate.export(spans); - } - - @Override - public CompletableResultCode flush() { - return CompletableResultCode.ofSuccess(); - } - - @Override - public CompletableResultCode shutdown() { - try { - delegate.shutdown(); - } catch (IOException e) { - return CompletableResultCode.ofFailure(); - } - return CompletableResultCode.ofSuccess(); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java deleted file mode 100644 index f877c7b7d..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/ExporterCallback.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.exporters; - -import io.opentelemetry.contrib.disk.buffering.SignalType; -import javax.annotation.Nullable; - -/** Notifies about exporter and storage-related operations from within a signal to disk exporter. */ -public interface ExporterCallback { - /** - * Called when an export to disk operation succeeded. - * - * @param type The type of signal associated to the exporter. - */ - void onExportSuccess(SignalType type); - - /** - * Called when an export to disk operation failed. - * - * @param type The type of signal associated to the exporter. - * @param error Optional - provides more information of why the operation failed. - */ - void onExportError(SignalType type, @Nullable Throwable error); - - /** - * Called when the exporter is closed. - * - * @param type The type of signal associated to the exporter. - */ - void onShutdown(SignalType type); - - static ExporterCallback noop() { - return NoopExporterCallback.INSTANCE; - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java new file mode 100644 index 000000000..6ed7ae2b4 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/LogRecordToDiskExporter.java @@ -0,0 +1,81 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.ExporterCallback; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.NoopExporterCallback; +import io.opentelemetry.contrib.disk.buffering.internal.exporters.SignalStorageExporter; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import io.opentelemetry.sdk.logs.export.LogRecordExporter; +import java.time.Duration; +import java.util.Collection; + +/** Exporter that stores logs into disk. */ +public final class LogRecordToDiskExporter implements LogRecordExporter { + private final SignalStorageExporter storageExporter; + private final ExporterCallback callback; + private static final ExporterCallback DEFAULT_CALLBACK = + new NoopExporterCallback<>(); + private static final Duration DEFAULT_EXPORT_TIMEOUT = Duration.ofSeconds(10); + + private LogRecordToDiskExporter( + SignalStorageExporter storageExporter, + ExporterCallback callback) { + this.storageExporter = storageExporter; + this.callback = callback; + } + + public static Builder builder(SignalStorage.LogRecord storage) { + return new Builder(storage); + } + + @Override + public CompletableResultCode export(Collection logs) { + return storageExporter.exportToStorage(logs); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + callback.onShutdown(); + return CompletableResultCode.ofSuccess(); + } + + public static final class Builder { + private final SignalStorage.LogRecord storage; + private ExporterCallback callback = DEFAULT_CALLBACK; + private Duration writeTimeout = DEFAULT_EXPORT_TIMEOUT; + + @CanIgnoreReturnValue + public Builder setExporterCallback(ExporterCallback value) { + callback = value; + return this; + } + + @CanIgnoreReturnValue + public Builder setWriteTimeout(Duration value) { + writeTimeout = value; + return this; + } + + public LogRecordToDiskExporter build() { + SignalStorageExporter storageExporter = + new SignalStorageExporter<>(storage, callback, writeTimeout); + return new LogRecordToDiskExporter(storageExporter, callback); + } + + private Builder(SignalStorage.LogRecord storage) { + this.storage = storage; + } + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java new file mode 100644 index 000000000..fe7a86abf --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/MetricToDiskExporter.java @@ -0,0 +1,99 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters; + +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.ExporterCallback; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.NoopExporterCallback; +import io.opentelemetry.contrib.disk.buffering.internal.exporters.SignalStorageExporter; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.AggregationTemporalitySelector; +import io.opentelemetry.sdk.metrics.export.MetricExporter; +import java.time.Duration; +import java.util.Collection; + +/** Exporter that stores metrics into disk. */ +public final class MetricToDiskExporter implements MetricExporter { + private final SignalStorageExporter storageExporter; + private final AggregationTemporalitySelector aggregationTemporalitySelector; + private final ExporterCallback callback; + private static final ExporterCallback DEFAULT_CALLBACK = new NoopExporterCallback<>(); + private static final Duration DEFAULT_EXPORT_TIMEOUT = Duration.ofSeconds(10); + + private MetricToDiskExporter( + SignalStorageExporter storageExporter, + AggregationTemporalitySelector aggregationTemporalitySelector, + ExporterCallback callback) { + this.storageExporter = storageExporter; + this.aggregationTemporalitySelector = aggregationTemporalitySelector; + this.callback = callback; + } + + public static Builder builder(SignalStorage.Metric storage) { + return new Builder(storage); + } + + @Override + public CompletableResultCode export(Collection metrics) { + return storageExporter.exportToStorage(metrics); + } + + @Override + public CompletableResultCode flush() { + return CompletableResultCode.ofSuccess(); + } + + @Override + public CompletableResultCode shutdown() { + callback.onShutdown(); + return CompletableResultCode.ofSuccess(); + } + + @Override + public AggregationTemporality getAggregationTemporality(InstrumentType instrumentType) { + return aggregationTemporalitySelector.getAggregationTemporality(instrumentType); + } + + public static final class Builder { + private final SignalStorage.Metric storage; + private AggregationTemporalitySelector aggregationTemporalitySelector = + AggregationTemporalitySelector.alwaysCumulative(); + private ExporterCallback callback = DEFAULT_CALLBACK; + private Duration writeTimeout = DEFAULT_EXPORT_TIMEOUT; + + @CanIgnoreReturnValue + public Builder setExporterCallback(ExporterCallback value) { + callback = value; + return this; + } + + @CanIgnoreReturnValue + public Builder setWriteTimeout(Duration value) { + writeTimeout = value; + return this; + } + + @CanIgnoreReturnValue + public Builder setAggregationTemporalitySelector(AggregationTemporalitySelector value) { + aggregationTemporalitySelector = value; + return this; + } + + public MetricToDiskExporter build() { + SignalStorageExporter storageExporter = + new SignalStorageExporter<>(storage, callback, writeTimeout); + return new MetricToDiskExporter(storageExporter, aggregationTemporalitySelector, callback); + } + + private Builder(SignalStorage.Metric storage) { + this.storage = storage; + } + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java deleted file mode 100644 index 2dd4f2f70..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/NoopExporterCallback.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.exporters; - -import io.opentelemetry.contrib.disk.buffering.SignalType; -import javax.annotation.Nullable; - -final class NoopExporterCallback implements ExporterCallback { - static final NoopExporterCallback INSTANCE = new NoopExporterCallback(); - - private NoopExporterCallback() {} - - @Override - public void onExportSuccess(SignalType type) {} - - @Override - public void onExportError(SignalType type, @Nullable Throwable error) {} - - @Override - public void onShutdown(SignalType type) {} -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java index 3efe5c367..9558a2767 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SpanToDiskExporter.java @@ -6,7 +6,9 @@ package io.opentelemetry.contrib.disk.buffering.exporters; import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.contrib.disk.buffering.SignalType; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.ExporterCallback; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.NoopExporterCallback; +import io.opentelemetry.contrib.disk.buffering.internal.exporters.SignalStorageExporter; import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; import io.opentelemetry.sdk.common.CompletableResultCode; import io.opentelemetry.sdk.trace.data.SpanData; @@ -16,13 +18,13 @@ /** Exporter that stores spans into disk. */ public final class SpanToDiskExporter implements SpanExporter { - private static final SignalType TYPE = SignalType.SPAN; - private final SignalStorageExporter storageExporter; - private final ExporterCallback callback; + private final ExporterCallback callback; + private static final ExporterCallback DEFAULT_CALLBACK = new NoopExporterCallback<>(); + private static final Duration DEFAULT_EXPORT_TIMEOUT = Duration.ofSeconds(10); private SpanToDiskExporter( - SignalStorageExporter storageExporter, ExporterCallback callback) { + SignalStorageExporter storageExporter, ExporterCallback callback) { this.storageExporter = storageExporter; this.callback = callback; } @@ -43,21 +45,21 @@ public CompletableResultCode flush() { @Override public CompletableResultCode shutdown() { - callback.onShutdown(TYPE); + callback.onShutdown(); return CompletableResultCode.ofSuccess(); } public static final class Builder { private final SignalStorage.Span storage; - private ExporterCallback callback = ExporterCallback.noop(); - private Duration writeTimeout = Duration.ofSeconds(10); + private ExporterCallback callback = DEFAULT_CALLBACK; + private Duration writeTimeout = DEFAULT_EXPORT_TIMEOUT; private Builder(SignalStorage.Span storage) { this.storage = storage; } @CanIgnoreReturnValue - public Builder setExporterCallback(ExporterCallback value) { + public Builder setExporterCallback(ExporterCallback value) { callback = value; return this; } @@ -70,7 +72,7 @@ public Builder setWriteTimeout(Duration value) { public SpanToDiskExporter build() { SignalStorageExporter storageExporter = - new SignalStorageExporter<>(storage, callback, writeTimeout, TYPE); + new SignalStorageExporter<>(storage, callback, writeTimeout); return new SpanToDiskExporter(storageExporter, callback); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/ExporterCallback.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/ExporterCallback.java new file mode 100644 index 000000000..9c3c816ea --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/ExporterCallback.java @@ -0,0 +1,30 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters.callback; + +import java.util.Collection; +import javax.annotation.Nullable; + +/** Notifies about exporter and storage-related operations from within a signal to disk exporter. */ +public interface ExporterCallback { + /** + * Called when an export to disk operation succeeded. + * + * @param items The items successfully stored in disk. + */ + void onExportSuccess(Collection items); + + /** + * Called when an export to disk operation failed. + * + * @param items The items that couldn't get stored in disk. + * @param error Optional - provides more information of why the operation failed. + */ + void onExportError(Collection items, @Nullable Throwable error); + + /** Called when the exporter is closed. */ + void onShutdown(); +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/NoopExporterCallback.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/NoopExporterCallback.java new file mode 100644 index 000000000..6313d1a5b --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/callback/NoopExporterCallback.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.exporters.callback; + +import java.util.Collection; +import javax.annotation.Nullable; + +public final class NoopExporterCallback implements ExporterCallback { + + @Override + public void onExportSuccess(Collection items) {} + + @Override + public void onExportError(Collection items, @Nullable Throwable error) {} + + @Override + public void onShutdown() {} +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporter.java deleted file mode 100644 index fdc3bb796..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporter.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import java.io.IOException; -import java.util.concurrent.TimeUnit; - -public interface FromDiskExporter { - boolean exportStoredBatch(long timeout, TimeUnit unit) throws IOException; - - void shutdown() throws IOException; -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java deleted file mode 100644 index a91ded1f3..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterBuilder.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import static java.util.Collections.emptyList; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.common.CompletableResultCode; -import java.io.IOException; -import java.util.Collection; -import java.util.function.Function; -import org.jetbrains.annotations.NotNull; - -public class FromDiskExporterBuilder { - - private SignalDeserializer serializer = noopDeserializer(); - private final Storage storage; - - private Function, CompletableResultCode> exportFunction = - x -> CompletableResultCode.ofFailure(); - - public FromDiskExporterBuilder(Storage storage) { - if (storage == null) { - throw new NullPointerException("Storage cannot be null"); - } - this.storage = storage; - } - - @NotNull - private static SignalDeserializer noopDeserializer() { - return x -> emptyList(); - } - - @CanIgnoreReturnValue - public FromDiskExporterBuilder setDeserializer(SignalDeserializer serializer) { - this.serializer = serializer; - return this; - } - - @CanIgnoreReturnValue - public FromDiskExporterBuilder setExportFunction( - Function, CompletableResultCode> exportFunction) { - this.exportFunction = exportFunction; - return this; - } - - public FromDiskExporterImpl build() throws IOException { - return new FromDiskExporterImpl<>(serializer, exportFunction, storage); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java deleted file mode 100644 index 5ba5c2390..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/FromDiskExporterImpl.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.DeserializationException; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; -import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; -import io.opentelemetry.contrib.disk.buffering.internal.utils.DebugLogger; -import io.opentelemetry.sdk.common.CompletableResultCode; -import java.io.IOException; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.logging.Logger; - -/** - * Signal-type generic class that can read telemetry previously buffered on disk and send it to - * another delegated exporter. - */ -public final class FromDiskExporterImpl implements FromDiskExporter { - private final DebugLogger logger; - private final Storage storage; - private final SignalDeserializer deserializer; - private final Function, CompletableResultCode> exportFunction; - - FromDiskExporterImpl( - SignalDeserializer deserializer, - Function, CompletableResultCode> exportFunction, - Storage storage) { - this.deserializer = deserializer; - this.exportFunction = exportFunction; - this.storage = storage; - this.logger = - DebugLogger.wrap( - Logger.getLogger(FromDiskExporterImpl.class.getName()), storage.isDebugEnabled()); - } - - public static FromDiskExporterBuilder builder(Storage storage) { - return new FromDiskExporterBuilder<>(storage); - } - - /** - * Reads data from the disk and attempts to export it. - * - * @param timeout The amount of time to wait for the wrapped exporter to finish. - * @param unit The unit of the time provided. - * @return true if there was data available, and it was successfully exported within the timeout - * provided. false otherwise. - * @throws IOException If an unexpected error happens. - */ - @Override - public boolean exportStoredBatch(long timeout, TimeUnit unit) throws IOException { - logger.log("Attempting to export " + deserializer.signalType() + " batch from disk."); - ReadableResult result = - storage.readAndProcess( - bytes -> { - logger.log( - "Read " - + bytes.length - + " " - + deserializer.signalType() - + " bytes from storage."); - try { - List telemetry = deserializer.deserialize(bytes); - logger.log( - "Now exporting batch of " + telemetry.size() + " " + deserializer.signalType()); - CompletableResultCode join = exportFunction.apply(telemetry).join(timeout, unit); - return join.isSuccess() ? ProcessResult.SUCCEEDED : ProcessResult.TRY_LATER; - } catch (DeserializationException e) { - return ProcessResult.CONTENT_INVALID; - } - }); - return result == ReadableResult.SUCCEEDED; - } - - @Override - public void shutdown() throws IOException { - storage.close(); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java deleted file mode 100644 index 715f538c4..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/NoopSerializer.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import java.io.IOException; -import java.io.OutputStream; -import java.util.Collection; - -class NoopSerializer implements SignalSerializer { - - @Override - public NoopSerializer initialize(Collection data) { - return this; - } - - @Override - public void writeBinaryTo(OutputStream output) throws IOException {} - - @Override - public int getBinarySerializedSize() { - return 0; - } - - @Override - public void reset() {} -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java deleted file mode 100644 index a3104a892..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporter.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import static java.util.logging.Level.FINER; -import static java.util.logging.Level.WARNING; - -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.contrib.disk.buffering.internal.utils.DebugLogger; -import io.opentelemetry.sdk.common.CompletableResultCode; -import java.io.IOException; -import java.util.Collection; -import java.util.function.Function; -import java.util.logging.Logger; - -public class ToDiskExporter { - - private final DebugLogger logger; - private final Storage storage; - private final SignalSerializer serializer; - private final Function, CompletableResultCode> exportFunction; - - ToDiskExporter( - SignalSerializer serializer, - Function, CompletableResultCode> exportFunction, - Storage storage) { - this.serializer = serializer; - this.exportFunction = exportFunction; - this.storage = storage; - this.logger = - DebugLogger.wrap( - Logger.getLogger(ToDiskExporter.class.getName()), storage.isDebugEnabled()); - } - - public static ToDiskExporterBuilder builder(Storage storage) { - return new ToDiskExporterBuilder<>(storage); - } - - public synchronized CompletableResultCode export(Collection data) { - logger.log("Intercepting exporter batch.", FINER); - try { - serializer.initialize(data); - if (storage.write(serializer)) { - return CompletableResultCode.ofSuccess(); - } - logger.log("Could not store batch in disk. Exporting it right away."); - return exportFunction.apply(data); - } catch (IOException e) { - logger.log( - "An unexpected error happened while attempting to write the data in disk. Exporting it right away.", - WARNING, - e); - return exportFunction.apply(data); - } finally { - serializer.reset(); - } - } - - public void shutdown() throws IOException { - storage.close(); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java deleted file mode 100644 index be75a3976..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterBuilder.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.common.CompletableResultCode; -import java.util.Collection; -import java.util.function.Function; - -public final class ToDiskExporterBuilder { - - private SignalSerializer serializer = new NoopSerializer(); - - private final Storage storage; - - private Function, CompletableResultCode> exportFunction = - x -> CompletableResultCode.ofFailure(); - - ToDiskExporterBuilder(Storage storage) { - if (storage == null) { - throw new NullPointerException("Storage cannot be null"); - } - this.storage = storage; - } - - @CanIgnoreReturnValue - public ToDiskExporterBuilder setSerializer(SignalSerializer serializer) { - this.serializer = serializer; - return this; - } - - @CanIgnoreReturnValue - public ToDiskExporterBuilder setExportFunction( - Function, CompletableResultCode> exportFunction) { - this.exportFunction = exportFunction; - return this; - } - - public ToDiskExporter build() { - return new ToDiskExporter<>(serializer, exportFunction, storage); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporter.java similarity index 76% rename from disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java rename to disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporter.java index c508d042f..22ba6b61c 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporter.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporter.java @@ -3,11 +3,11 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.contrib.disk.buffering.exporters; +package io.opentelemetry.contrib.disk.buffering.internal.exporters; import static java.util.concurrent.TimeUnit.MILLISECONDS; -import io.opentelemetry.contrib.disk.buffering.SignalType; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.ExporterCallback; import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; import io.opentelemetry.sdk.common.CompletableResultCode; @@ -18,18 +18,16 @@ import java.util.concurrent.TimeoutException; /** Internal utility for common export to disk operations across all exporters. */ -final class SignalStorageExporter { +public final class SignalStorageExporter { private final SignalStorage storage; - private final ExporterCallback callback; + private final ExporterCallback callback; private final Duration writeTimeout; - private final SignalType type; public SignalStorageExporter( - SignalStorage storage, ExporterCallback callback, Duration writeTimeout, SignalType type) { + SignalStorage storage, ExporterCallback callback, Duration writeTimeout) { this.storage = storage; this.callback = callback; this.writeTimeout = writeTimeout; - this.type = type; } public CompletableResultCode exportToStorage(Collection items) { @@ -37,18 +35,18 @@ public CompletableResultCode exportToStorage(Collection items) { try { WriteResult operation = future.get(writeTimeout.toMillis(), MILLISECONDS); if (operation.isSuccessful()) { - callback.onExportSuccess(type); + callback.onExportSuccess(items); return CompletableResultCode.ofSuccess(); } Throwable error = operation.getError(); - callback.onExportError(type, error); + callback.onExportError(items, error); if (error != null) { return CompletableResultCode.ofExceptionalFailure(error); } return CompletableResultCode.ofFailure(); } catch (ExecutionException | InterruptedException | TimeoutException e) { - callback.onExportError(type, e); + callback.onExportError(items, e); return CompletableResultCode.ofExceptionalFailure(e); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java index 075a0f103..d4c877923 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/LogRecordDataDeserializer.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.ProtoLogsDataMapper; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.proto.collector.logs.v1.ExportLogsServiceRequest; import io.opentelemetry.sdk.logs.data.LogRecordData; import java.io.IOException; @@ -30,9 +29,4 @@ public List deserialize(byte[] source) throws DeserializationExce throw new DeserializationException(e); } } - - @Override - public String signalType() { - return SignalTypes.logs.name(); - } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java index 463c07b75..eda886f89 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/MetricDataDeserializer.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.metrics.ProtoMetricsDataMapper; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest; import io.opentelemetry.sdk.metrics.data.MetricData; import java.io.IOException; @@ -30,9 +29,4 @@ public List deserialize(byte[] source) throws DeserializationExcepti throw new DeserializationException(e); } } - - @Override - public String signalType() { - return SignalTypes.metrics.name(); - } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SignalDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SignalDeserializer.java index dd56e356e..915868288 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SignalDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SignalDeserializer.java @@ -26,9 +26,4 @@ static SignalDeserializer ofLogs() { /** Deserializes the given byte array into a list of telemetry items. */ List deserialize(byte[] source) throws DeserializationException; - - /** Returns the name of the stored type of signal -- one of "metrics", "spans", or "logs". */ - default String signalType() { - return "unknown"; - } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java index b703c3ade..b3631ca4c 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/serialization/deserializers/SpanDataDeserializer.java @@ -6,7 +6,6 @@ package io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers; import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.ProtoSpansDataMapper; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import io.opentelemetry.proto.collector.trace.v1.ExportTraceServiceRequest; import io.opentelemetry.sdk.trace.data.SpanData; import java.io.IOException; @@ -30,9 +29,4 @@ public List deserialize(byte[] source) throws DeserializationException throw new DeserializationException(e); } } - - @Override - public String signalType() { - return SignalTypes.spans.name(); - } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSignalStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSignalStorage.java new file mode 100644 index 000000000..743dee757 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSignalStorage.java @@ -0,0 +1,92 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.storage; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +/** Default storage implementation where items are stored in multiple protobuf files. */ +public final class FileSignalStorage implements SignalStorage { + private final Storage storage; + private final SignalSerializer serializer; + private final SignalDeserializer deserializer; + private final Logger logger = Logger.getLogger(FileSignalStorage.class.getName()); + private final AtomicBoolean isClosed = new AtomicBoolean(false); + private final Object iteratorLock = new Object(); + + @GuardedBy("iteratorLock") + @Nullable + private Iterator> iterator; + + public FileSignalStorage( + Storage storage, SignalSerializer serializer, SignalDeserializer deserializer) { + this.storage = storage; + this.serializer = serializer; + this.deserializer = deserializer; + } + + @Override + public CompletableFuture write(Collection items) { + logger.finer("Intercepting batch."); + try { + serializer.initialize(items); + if (storage.write(serializer)) { + return CompletableFuture.completedFuture(WriteResult.successful()); + } + logger.fine("Could not store batch in disk."); + return CompletableFuture.completedFuture( + WriteResult.error(new Exception("Could not store batch in disk for an unknown reason."))); + } catch (IOException e) { + logger.log( + Level.WARNING, + "An unexpected error happened while attempting to write the data in disk.", + e); + return CompletableFuture.completedFuture(WriteResult.error(e)); + } finally { + serializer.reset(); + } + } + + @Override + public CompletableFuture clear() { + try { + storage.clear(); + return CompletableFuture.completedFuture(WriteResult.successful()); + } catch (IOException e) { + return CompletableFuture.completedFuture(WriteResult.error(e)); + } + } + + @Override + public void close() throws IOException { + if (isClosed.compareAndSet(false, true)) { + storage.close(); + } + } + + @Nonnull + @Override + public Iterator> iterator() { + synchronized (iteratorLock) { + if (iterator == null) { + iterator = new StorageIterator<>(storage, deserializer); + } + return iterator; + } + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java deleted file mode 100644 index 5ba51790f..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FileSpanStorage.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.storage; - -import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; -import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; -import io.opentelemetry.sdk.trace.data.SpanData; -import java.util.Collection; -import java.util.Iterator; -import java.util.concurrent.CompletableFuture; -import javax.annotation.Nonnull; - -/** Default storage implementation where items are stored in multiple protobuf files. */ -public final class FileSpanStorage implements SignalStorage.Span { - - @Override - public CompletableFuture write(Collection items) { - throw new UnsupportedOperationException("For next PR"); - } - - @Override - public CompletableFuture clear() { - throw new UnsupportedOperationException("For next PR"); - } - - @Override - public void close() { - throw new UnsupportedOperationException("For next PR"); - } - - @Nonnull - @Override - public Iterator> iterator() { - throw new UnsupportedOperationException("For next PR"); - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java index 1f76419ab..d93751fa1 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManager.java @@ -7,13 +7,15 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.util.ClockBuddy.nowMillis; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileStorageConfiguration; import io.opentelemetry.sdk.common.Clock; import java.io.Closeable; import java.io.File; import java.io.IOException; +import java.util.ArrayList; +import java.util.List; import java.util.Objects; import javax.annotation.Nullable; import org.jetbrains.annotations.NotNull; @@ -21,11 +23,24 @@ public final class FolderManager implements Closeable { private final File folder; private final Clock clock; - private final StorageConfiguration configuration; + private final FileStorageConfiguration configuration; @Nullable private ReadableFile currentReadableFile; @Nullable private WritableFile currentWritableFile; - public FolderManager(File folder, StorageConfiguration configuration, Clock clock) { + public static FolderManager create( + File destinationDir, FileStorageConfiguration configuration, Clock clock) { + if (destinationDir.isFile()) { + throw new IllegalArgumentException("destinationDir must be a directory"); + } + if (!destinationDir.exists()) { + if (!destinationDir.mkdirs()) { + throw new IllegalStateException("Could not create dir: " + destinationDir); + } + } + return new FolderManager(destinationDir, configuration, clock); + } + + public FolderManager(File folder, FileStorageConfiguration configuration, Clock clock) { this.folder = folder; this.configuration = configuration; this.clock = clock; @@ -33,12 +48,7 @@ public FolderManager(File folder, StorageConfiguration configuration, Clock cloc @Override public void close() throws IOException { - if (currentReadableFile != null) { - currentReadableFile.close(); - } - if (currentWritableFile != null) { - currentWritableFile.close(); - } + closeCurrentFiles(); } @Nullable @@ -68,6 +78,21 @@ public synchronized WritableFile createWritableFile() throws IOException { return currentWritableFile; } + public synchronized void clear() throws IOException { + closeCurrentFiles(); + List undeletedFiles = new ArrayList<>(); + + for (File file : Objects.requireNonNull(folder.listFiles())) { + if (!file.delete()) { + undeletedFiles.add(file); + } + } + + if (!undeletedFiles.isEmpty()) { + throw new IOException("Could not delete files " + undeletedFiles); + } + } + @Nullable private File findReadableFile() throws IOException { long currentTime = nowMillis(clock); @@ -152,4 +177,13 @@ private boolean hasExpiredForReading(long systemCurrentTimeMillis, long createdT return systemCurrentTimeMillis > (createdTimeInMillis + configuration.getMaxFileAgeForReadMillis()); } + + private synchronized void closeCurrentFiles() throws IOException { + if (currentReadableFile != null) { + currentReadableFile.close(); + } + if (currentWritableFile != null) { + currentWritableFile.close(); + } + } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java index 86b5284ca..6b2ee05f7 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/Storage.java @@ -7,44 +7,34 @@ import static java.util.logging.Level.WARNING; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.DeserializationException; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; -import io.opentelemetry.contrib.disk.buffering.internal.utils.DebugLogger; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; import java.io.Closeable; import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Function; +import java.util.concurrent.atomic.AtomicReference; import java.util.logging.Logger; import javax.annotation.Nullable; -public final class Storage implements Closeable { +public final class Storage implements Closeable { private static final int MAX_ATTEMPTS = 3; - private final DebugLogger logger; + private final Logger logger = Logger.getLogger(Storage.class.getName()); private final FolderManager folderManager; - private final boolean debugEnabled; private final AtomicBoolean isClosed = new AtomicBoolean(false); - @Nullable private WritableFile writableFile; - @Nullable private ReadableFile readableFile; + private final AtomicBoolean activeReadResultAvailable = new AtomicBoolean(false); + private final AtomicReference writableFileRef = new AtomicReference<>(); + private final AtomicReference readableFileRef = new AtomicReference<>(); - public Storage(FolderManager folderManager, boolean debugEnabled) { + public Storage(FolderManager folderManager) { this.folderManager = folderManager; - this.logger = - DebugLogger.wrap(Logger.getLogger(FromDiskExporterImpl.class.getName()), debugEnabled); - this.debugEnabled = debugEnabled; - } - - public static StorageBuilder builder(SignalTypes types) { - return new StorageBuilder(types); - } - - public boolean isDebugEnabled() { - return debugEnabled; } /** @@ -53,91 +43,151 @@ public boolean isDebugEnabled() { * @param marshaler - The data that would be appended to the file. * @throws IOException If an unexpected error happens. */ - public boolean write(SignalSerializer marshaler) throws IOException { + public boolean write(SignalSerializer marshaler) throws IOException { return write(marshaler, 1); } - private boolean write(SignalSerializer marshaler, int attemptNumber) throws IOException { + private boolean write(SignalSerializer marshaler, int attemptNumber) throws IOException { if (isClosed.get()) { - logger.log("Refusing to write to storage after being closed."); + logger.fine("Refusing to write to storage after being closed."); return false; } if (attemptNumber > MAX_ATTEMPTS) { - logger.log("Max number of attempts to write buffered data exceeded.", WARNING); + logger.log(WARNING, "Max number of attempts to write buffered data exceeded."); return false; } + WritableFile writableFile = writableFileRef.get(); if (writableFile == null) { writableFile = folderManager.createWritableFile(); - logger.log("Created new writableFile: " + writableFile); + writableFileRef.set(writableFile); + logger.finer("Created new writableFile: " + writableFile); } WritableResult result = writableFile.append(marshaler); if (result != WritableResult.SUCCEEDED) { // Retry with new file - writableFile = null; + writableFileRef.set(null); return write(marshaler, ++attemptNumber); } return true; } public void flush() throws IOException { + WritableFile writableFile = writableFileRef.get(); if (writableFile != null) { writableFile.flush(); } else { - logger.log("No writable file to flush."); + logger.info("No writable file to flush."); } } /** * Attempts to read an item from a ready-to-read file. * - * @param processing Is passed over to {@link ReadableFile#readAndProcess(Function)}. * @throws IOException If an unexpected error happens. */ - public ReadableResult readAndProcess(Function processing) - throws IOException { - return readAndProcess(processing, 1); + @Nullable + public ReadableResult readNext(SignalDeserializer deserializer) throws IOException { + if (activeReadResultAvailable.get()) { + throw new IllegalStateException( + "You must close any previous ReadableResult before requesting a new one"); + } + return doReadNext(deserializer, 1); } - private ReadableResult readAndProcess( - Function processing, int attemptNumber) throws IOException { + @Nullable + private ReadableResult doReadNext(SignalDeserializer deserializer, int attemptNumber) + throws IOException { if (isClosed.get()) { - logger.log("Refusing to read from storage after being closed."); - return ReadableResult.FAILED; + logger.fine("Refusing to read from storage after being closed."); + return null; } if (attemptNumber > MAX_ATTEMPTS) { - logger.log("Maximum number of attempts to read and process buffered data exceeded.", WARNING); - return ReadableResult.FAILED; + logger.log(WARNING, "Maximum number of attempts to read buffered data exceeded."); + return null; } + ReadableFile readableFile = readableFileRef.get(); if (readableFile == null) { - logger.log("Obtaining a new readableFile from the folderManager."); + logger.finer("Obtaining a new readableFile from the folderManager."); readableFile = folderManager.getReadableFile(); + readableFileRef.set(readableFile); if (readableFile == null) { - logger.log("Unable to get or create readable file."); - return ReadableResult.FAILED; + logger.fine("Unable to get or create readable file."); + return null; } } - logger.log("Attempting to read data from " + readableFile); - ReadableResult result = readableFile.readAndProcess(processing); - switch (result) { - case SUCCEEDED: - case TRY_LATER: - return result; - default: - // Retry with new file - readableFile = null; - return readAndProcess(processing, ++attemptNumber); + + logger.finer("Attempting to read data from " + readableFile); + byte[] result = readableFile.readNext(); + if (result != null) { + try { + List items = deserializer.deserialize(result); + activeReadResultAvailable.set(true); + return new FileReadResult(items, readableFile); + } catch (DeserializationException e) { + // Data corrupted, clear file. + readableFile.clear(); + } } + + // Retry with new file + readableFileRef.set(null); + return doReadNext(deserializer, ++attemptNumber); + } + + public void clear() throws IOException { + folderManager.clear(); + } + + public boolean isClosed() { + return isClosed.get(); } @Override public void close() throws IOException { - logger.log("Closing disk buffering storage."); + logger.fine("Closing disk buffering storage."); if (isClosed.compareAndSet(false, true)) { - if (writableFile != null) { - writableFile.close(); + folderManager.close(); + writableFileRef.set(null); + readableFileRef.set(null); + } + } + + class FileReadResult implements ReadableResult { + private final Collection content; + private final AtomicBoolean itemDeleted = new AtomicBoolean(false); + private final AtomicBoolean closed = new AtomicBoolean(false); + private final AtomicReference readableFile = new AtomicReference<>(); + + FileReadResult(Collection content, ReadableFile readableFile) { + this.content = content; + this.readableFile.set(readableFile); + } + + @Override + public Collection getContent() { + return content; + } + + @Override + public void delete() throws IOException { + if (closed.get()) { + return; } - if (readableFile != null) { - readableFile.close(); + if (itemDeleted.compareAndSet(false, true)) { + try { + Objects.requireNonNull(readableFile.get()).removeTopItem(); + } catch (IOException e) { + itemDeleted.set(false); + throw e; + } + } + } + + @Override + public void close() throws IOException { + if (closed.compareAndSet(false, true)) { + activeReadResultAvailable.set(false); + readableFile.set(null); } } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java deleted file mode 100644 index ebea37171..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageBuilder.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.storage; - -import static java.util.logging.Level.INFO; - -import com.google.errorprone.annotations.CanIgnoreReturnValue; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.sdk.common.Clock; -import java.io.File; -import java.io.IOException; -import java.util.logging.Logger; - -public class StorageBuilder { - - private static final Logger logger = Logger.getLogger(StorageBuilder.class.getName()); - - private final String folderName; - private StorageConfiguration configuration = StorageConfiguration.getDefault(new File(".")); - private Clock clock = Clock.getDefault(); - - StorageBuilder(SignalTypes types) { - folderName = types.name(); - } - - @CanIgnoreReturnValue - public StorageBuilder setStorageConfiguration(StorageConfiguration configuration) { - validateConfiguration(configuration); - this.configuration = configuration; - return this; - } - - @CanIgnoreReturnValue - public StorageBuilder setStorageClock(Clock clock) { - this.clock = clock; - return this; - } - - public Storage build() throws IOException { - File folder = ensureSubdir(configuration.getRootDir(), folderName); - FolderManager folderManager = new FolderManager(folder, configuration, clock); - if (configuration.isDebugEnabled()) { - logger.log(INFO, "Building storage with configuration => " + configuration); - } - return new Storage(folderManager, configuration.isDebugEnabled()); - } - - private static File ensureSubdir(File rootDir, String child) throws IOException { - File subdir = new File(rootDir, child); - if (subdir.exists() || subdir.mkdirs()) { - return subdir; - } - throw new IOException("Could not create the subdir: '" + child + "' inside: " + rootDir); - } - - private static void validateConfiguration(StorageConfiguration configuration) { - if (configuration.getMinFileAgeForReadMillis() <= configuration.getMaxFileAgeForWriteMillis()) { - throw new IllegalArgumentException( - "The configured max file age for writing must be lower than the configured min file age for reading"); - } - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageIterator.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageIterator.java new file mode 100644 index 000000000..871238b03 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageIterator.java @@ -0,0 +1,90 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.internal.storage; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; +import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Objects; +import java.util.logging.Level; +import java.util.logging.Logger; +import javax.annotation.Nullable; +import javax.annotation.concurrent.GuardedBy; + +final class StorageIterator implements Iterator> { + private final Storage storage; + private final SignalDeserializer deserializer; + private final Logger logger = Logger.getLogger(StorageIterator.class.getName()); + + @GuardedBy("this") + @Nullable + private ReadableResult currentResult; + + @GuardedBy("this") + private boolean currentResultConsumed = false; + + StorageIterator(Storage storage, SignalDeserializer deserializer) { + this.storage = storage; + this.deserializer = deserializer; + } + + @Override + public synchronized boolean hasNext() { + if (storage.isClosed()) { + return false; + } + return findNext(); + } + + @Override + @Nullable + public synchronized Collection next() { + if (storage.isClosed()) { + return null; + } + if (findNext()) { + currentResultConsumed = true; + return Objects.requireNonNull(currentResult).getContent(); + } + return null; + } + + @Override + public synchronized void remove() { + if (currentResult != null) { + try { + currentResult.delete(); + } catch (IOException e) { + logger.log(Level.SEVERE, "Error deleting stored item", e); + } + } + } + + private synchronized boolean findNext() { + try { + if (currentResult != null) { + if (!currentResultConsumed) { + return true; + } + currentResult.delete(); + currentResult.close(); + currentResult = null; + } + + currentResultConsumed = false; + ReadableResult result = storage.readNext(deserializer); + if (result != null) { + currentResult = result; + return true; + } + } catch (IOException e) { + logger.log(Level.SEVERE, "Error reading from storage", e); + } + return false; + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java index 710e192bb..59429d187 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFile.java @@ -7,20 +7,17 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.util.ClockBuddy.nowMillis; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.DelimitedProtoStreamReader; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ReadResult; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.StreamReader; import io.opentelemetry.contrib.disk.buffering.internal.storage.files.utils.FileStream; -import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileStorageConfiguration; import io.opentelemetry.sdk.common.Clock; import java.io.File; import java.io.IOException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; +import javax.annotation.Nonnull; import javax.annotation.Nullable; -import org.jetbrains.annotations.NotNull; /** * Reads from a file and updates it in parallel in order to avoid re-reading the same items later. @@ -32,16 +29,15 @@ *

More information on the overall storage process in the CONTRIBUTING.md file. */ public final class ReadableFile implements FileOperations { - @NotNull private final File file; + @Nonnull private final File file; private final FileStream fileStream; private final StreamReader reader; private final Clock clock; private final long expireTimeMillis; private final AtomicBoolean isClosed = new AtomicBoolean(false); - @Nullable private ReadResult unconsumedResult; public ReadableFile( - File file, long createdTimeMillis, Clock clock, StorageConfiguration configuration) + File file, long createdTimeMillis, Clock clock, FileStorageConfiguration configuration) throws IOException { this( file, @@ -52,10 +48,10 @@ public ReadableFile( } public ReadableFile( - @NotNull File file, + @Nonnull File file, long createdTimeMillis, Clock clock, - StorageConfiguration configuration, + FileStorageConfiguration configuration, StreamReader.Factory readerFactory) throws IOException { this.file = file; @@ -68,49 +64,22 @@ public ReadableFile( /** * Reads the next line available in the file and provides it to a {@link Function processing} * which will determine whether to remove the provided line or not. - * - * @param processing - A function that receives the line that has been read and returns a boolean. - * If the processing function returns TRUE, then the provided line will be deleted from the - * source file. If the function returns FALSE, no changes will be applied to the source file. */ - public synchronized ReadableResult readAndProcess(Function processing) - throws IOException { + @Nullable + public synchronized byte[] readNext() throws IOException { if (isClosed.get()) { - return ReadableResult.FAILED; + return null; } if (hasExpired()) { close(); - return ReadableResult.FAILED; - } - ReadResult read = readNextItem(); - if (read == null) { - cleanUp(); - return ReadableResult.FAILED; + return null; } - switch (processing.apply(read.content)) { - case SUCCEEDED: - unconsumedResult = null; - fileStream.truncateTop(); - if (fileStream.size() == 0) { - cleanUp(); - } - return ReadableResult.SUCCEEDED; - case TRY_LATER: - unconsumedResult = read; - return ReadableResult.TRY_LATER; - case CONTENT_INVALID: - cleanUp(); - return ReadableResult.FAILED; - } - return ReadableResult.FAILED; - } - - @Nullable - private ReadResult readNextItem() throws IOException { - if (unconsumedResult != null) { - return unconsumedResult; + byte[] resultBytes = reader.readNext(); + if (resultBytes == null) { + clear(); + return null; } - return reader.readNext(); + return resultBytes; } @Override @@ -123,23 +92,29 @@ public synchronized boolean isClosed() { return isClosed.get(); } - @NotNull + @Nonnull @Override public File getFile() { return file; } - private void cleanUp() throws IOException { + public synchronized void clear() throws IOException { close(); if (!file.delete()) { throw new IOException("Could not delete file: " + file); } } + public synchronized void removeTopItem() throws IOException { + fileStream.truncateTop(); + if (fileStream.size() == 0) { + clear(); + } + } + @Override public synchronized void close() throws IOException { if (isClosed.compareAndSet(false, true)) { - unconsumedResult = null; reader.close(); } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java index ce4e87ddf..325d30856 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFile.java @@ -7,9 +7,9 @@ import static io.opentelemetry.contrib.disk.buffering.internal.storage.util.ClockBuddy.nowMillis; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileStorageConfiguration; import io.opentelemetry.sdk.common.Clock; import java.io.File; import java.io.FileOutputStream; @@ -21,7 +21,7 @@ public final class WritableFile implements FileOperations { private final File file; - private final StorageConfiguration configuration; + private final FileStorageConfiguration configuration; private final Clock clock; private final long expireTimeMillis; private final OutputStream out; @@ -29,7 +29,7 @@ public final class WritableFile implements FileOperations { private int size; public WritableFile( - File file, long createdTimeMillis, StorageConfiguration configuration, Clock clock) + File file, long createdTimeMillis, FileStorageConfiguration configuration, Clock clock) throws IOException { this.file = file; this.configuration = configuration; diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java index 60a8e4f45..d638c118b 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/DelimitedProtoStreamReader.java @@ -19,16 +19,22 @@ public DelimitedProtoStreamReader(InputStream inputStream) { @Override @Nullable - public ReadResult readNext() throws IOException { + public byte[] readNext() throws IOException { int itemSize = getNextItemSize(); if (itemSize < 1) { return null; } byte[] bytes = new byte[itemSize]; - if (inputStream.read(bytes) < 0) { - return null; + int offset = 0; + int readCt; + do { + readCt = inputStream.read(bytes, offset, itemSize - offset); + offset += readCt; + } while (readCt != -1 && offset < itemSize); + if (offset != itemSize) { + return null; // unable to read the whole item correctly } - return new ReadResult(bytes); + return bytes; } private int getNextItemSize() { diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ProcessResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ProcessResult.java deleted file mode 100644 index 696d98d2c..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ProcessResult.java +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader; - -/** Result of processing the contents of a file. */ -public enum ProcessResult { - SUCCEEDED, - TRY_LATER, - CONTENT_INVALID -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java deleted file mode 100644 index a9f5d1116..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/ReadResult.java +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader; - -public final class ReadResult { - /** The consumable data. */ - public final byte[] content; - - public ReadResult(byte[] content) { - this.content = content; - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java index 447315f1e..925422f67 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/reader/StreamReader.java @@ -12,7 +12,7 @@ public interface StreamReader extends Closeable { @Nullable - ReadResult readNext() throws IOException; + byte[] readNext() throws IOException; interface Factory { StreamReader create(InputStream stream); diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/responses/ReadableResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/responses/ReadableResult.java index 295bc2289..1e2877c7b 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/responses/ReadableResult.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/storage/responses/ReadableResult.java @@ -5,8 +5,14 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage.responses; -public enum ReadableResult { - SUCCEEDED, - FAILED, - TRY_LATER +import java.io.Closeable; +import java.io.IOException; +import java.util.Collection; + +public interface ReadableResult extends Closeable { + /** The consumable data. */ + Collection getContent(); + + /** Delete the items provided in {@link #getContent()} */ + void delete() throws IOException; } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java deleted file mode 100644 index b0bb67624..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/DebugLogger.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.utils; - -import static java.util.logging.Level.INFO; - -import java.util.logging.Level; -import java.util.logging.Logger; - -public class DebugLogger { - private final Logger logger; - private final boolean debugEnabled; - - private DebugLogger(Logger logger, boolean debugEnabled) { - this.logger = logger; - this.debugEnabled = debugEnabled; - } - - public static DebugLogger wrap(Logger logger, boolean debugEnabled) { - return new DebugLogger(logger, debugEnabled); - } - - public void log(String msg) { - log(msg, INFO); - } - - public void log(String msg, Level level) { - if (debugEnabled) { - logger.log(level, msg); - } - } - - public void log(String msg, Level level, Throwable e) { - if (debugEnabled) { - logger.log(level, msg, e); - } - } -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/SignalTypes.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/SignalTypes.java deleted file mode 100644 index c0a7f5765..000000000 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/internal/utils/SignalTypes.java +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.utils; - -public enum SignalTypes { - metrics, - spans, - logs -} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java new file mode 100644 index 000000000..71d5d884b --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileLogRecordStorage.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.storage.impl; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.internal.storage.FileSignalStorage; +import io.opentelemetry.contrib.disk.buffering.internal.storage.FolderManager; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.logs.data.LogRecordData; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public final class FileLogRecordStorage implements SignalStorage.LogRecord { + private final FileSignalStorage fileSignalStorage; + + public static FileLogRecordStorage create(File destinationDir) { + return create(destinationDir, FileStorageConfiguration.getDefault()); + } + + public static FileLogRecordStorage create( + File destinationDir, FileStorageConfiguration configuration) { + Storage storage = + new Storage<>(FolderManager.create(destinationDir, configuration, Clock.getDefault())); + return new FileLogRecordStorage( + new FileSignalStorage<>(storage, SignalSerializer.ofLogs(), SignalDeserializer.ofLogs())); + } + + private FileLogRecordStorage(FileSignalStorage fileSignalStorage) { + this.fileSignalStorage = fileSignalStorage; + } + + @Override + public CompletableFuture write(Collection items) { + return fileSignalStorage.write(items); + } + + @Override + public CompletableFuture clear() { + return fileSignalStorage.clear(); + } + + @Override + public void close() throws IOException { + fileSignalStorage.close(); + } + + @Nonnull + @Override + public Iterator> iterator() { + return fileSignalStorage.iterator(); + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java new file mode 100644 index 000000000..8f8b41508 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileMetricStorage.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.storage.impl; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.internal.storage.FileSignalStorage; +import io.opentelemetry.contrib.disk.buffering.internal.storage.FolderManager; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.metrics.data.MetricData; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public final class FileMetricStorage implements SignalStorage.Metric { + private final FileSignalStorage fileSignalStorage; + + public static FileMetricStorage create(File destinationDir) { + return create(destinationDir, FileStorageConfiguration.getDefault()); + } + + public static FileMetricStorage create( + File destinationDir, FileStorageConfiguration configuration) { + Storage storage = + new Storage<>(FolderManager.create(destinationDir, configuration, Clock.getDefault())); + return new FileMetricStorage( + new FileSignalStorage<>( + storage, SignalSerializer.ofMetrics(), SignalDeserializer.ofMetrics())); + } + + private FileMetricStorage(FileSignalStorage fileSignalStorage) { + this.fileSignalStorage = fileSignalStorage; + } + + @Override + public CompletableFuture write(Collection items) { + return fileSignalStorage.write(items); + } + + @Override + public CompletableFuture clear() { + return fileSignalStorage.clear(); + } + + @Override + public void close() throws IOException { + fileSignalStorage.close(); + } + + @Nonnull + @Override + public Iterator> iterator() { + return fileSignalStorage.iterator(); + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java new file mode 100644 index 000000000..b8cfa8996 --- /dev/null +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileSpanStorage.java @@ -0,0 +1,63 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.disk.buffering.storage.impl; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; +import io.opentelemetry.contrib.disk.buffering.internal.storage.FileSignalStorage; +import io.opentelemetry.contrib.disk.buffering.internal.storage.FolderManager; +import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.trace.data.SpanData; +import java.io.File; +import java.io.IOException; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.CompletableFuture; +import javax.annotation.Nonnull; + +public final class FileSpanStorage implements SignalStorage.Span { + private final FileSignalStorage fileSignalStorage; + + public static FileSpanStorage create(File destinationDir) { + return create(destinationDir, FileStorageConfiguration.getDefault()); + } + + public static FileSpanStorage create( + File destinationDir, FileStorageConfiguration configuration) { + Storage storage = + new Storage<>(FolderManager.create(destinationDir, configuration, Clock.getDefault())); + return new FileSpanStorage( + new FileSignalStorage<>(storage, SignalSerializer.ofSpans(), SignalDeserializer.ofSpans())); + } + + private FileSpanStorage(FileSignalStorage fileSignalStorage) { + this.fileSignalStorage = fileSignalStorage; + } + + @Override + public CompletableFuture write(Collection items) { + return fileSignalStorage.write(items); + } + + @Override + public CompletableFuture clear() { + return fileSignalStorage.clear(); + } + + @Override + public void close() throws IOException { + fileSignalStorage.close(); + } + + @Nonnull + @Override + public Iterator> iterator() { + return fileSignalStorage.iterator(); + } +} diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileStorageConfiguration.java similarity index 72% rename from disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java rename to disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileStorageConfiguration.java index e8a7f0bd0..0a34c12b4 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/config/StorageConfiguration.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/impl/FileStorageConfiguration.java @@ -3,23 +3,16 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.contrib.disk.buffering.config; +package io.opentelemetry.contrib.disk.buffering.storage.impl; import static java.util.concurrent.TimeUnit.HOURS; import static java.util.concurrent.TimeUnit.SECONDS; import com.google.auto.value.AutoValue; -import java.io.File; /** Defines how the storage should be managed. */ @AutoValue -public abstract class StorageConfiguration { - - /** The root storage location for buffered telemetry. */ - public abstract File getRootDir(); - - /** Returns true if the storage has been configured with debug verbosity enabled. */ - public abstract boolean isDebugEnabled(); +public abstract class FileStorageConfiguration { /** The max amount of time a file can receive new data. */ public abstract long getMaxFileAgeForWriteMillis(); @@ -50,18 +43,17 @@ public abstract class StorageConfiguration { */ public abstract int getMaxFolderSize(); - public static StorageConfiguration getDefault(File rootDir) { - return builder().setRootDir(rootDir).build(); + public static FileStorageConfiguration getDefault() { + return builder().build(); } public static Builder builder() { - return new AutoValue_StorageConfiguration.Builder() + return new AutoValue_FileStorageConfiguration.Builder() .setMaxFileSize(1024 * 1024) // 1MB .setMaxFolderSize(10 * 1024 * 1024) // 10MB .setMaxFileAgeForWriteMillis(SECONDS.toMillis(30)) .setMinFileAgeForReadMillis(SECONDS.toMillis(33)) - .setMaxFileAgeForReadMillis(HOURS.toMillis(18)) - .setDebugEnabled(false); + .setMaxFileAgeForReadMillis(HOURS.toMillis(18)); } @AutoValue.Builder @@ -76,10 +68,16 @@ public abstract static class Builder { public abstract Builder setMaxFolderSize(int value); - public abstract Builder setRootDir(File rootDir); - - public abstract Builder setDebugEnabled(boolean debugEnabled); - - public abstract StorageConfiguration build(); + abstract FileStorageConfiguration autoBuild(); + + public final FileStorageConfiguration build() { + FileStorageConfiguration configuration = autoBuild(); + if (configuration.getMinFileAgeForReadMillis() + <= configuration.getMaxFileAgeForWriteMillis()) { + throw new IllegalArgumentException( + "The configured max file age for writing must be lower than the configured min file age for reading"); + } + return configuration; + } } } diff --git a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java index 8e4534a55..85c08ed37 100644 --- a/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java +++ b/disk-buffering/src/main/java/io/opentelemetry/contrib/disk/buffering/storage/result/WriteResult.java @@ -25,7 +25,11 @@ public interface WriteResult { @Nullable Throwable getError(); - static WriteResult create(boolean successful, @Nullable Throwable error) { - return new DefaultWriteResult(successful, error); + static WriteResult successful() { + return new DefaultWriteResult(/* successful= */ true, null); + } + + static WriteResult error(@Nullable Throwable t) { + return new DefaultWriteResult(/* successful= */ false, t); } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java deleted file mode 100644 index e7995c675..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/FromDiskExporterImplTest.java +++ /dev/null @@ -1,114 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MIN_FILE_AGE_FOR_READ_MILLIS; -import static java.util.Collections.singletonList; -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.DeserializationException; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.TestData; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.sdk.common.Clock; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -@SuppressWarnings("unchecked") -class FromDiskExporterImplTest { - private SpanExporter wrapped; - private SignalDeserializer deserializer; - private Clock clock; - private FromDiskExporterImpl exporter; - private final List deserializedData = Collections.emptyList(); - @TempDir File rootDir; - private static final String STORAGE_FOLDER_NAME = SignalTypes.spans.name(); - - @BeforeEach - void setUp() throws IOException { - clock = createClockMock(); - - setUpSerializer(); - wrapped = mock(); - exporter = - FromDiskExporterImpl.builder( - TestData.getStorage(rootDir, SignalTypes.spans, clock)) - .setDeserializer(deserializer) - .setExportFunction(wrapped::export) - .build(); - } - - @Test - void whenExportingStoredBatch_withAvailableData_andSuccessfullyProcessed_returnTrue() - throws IOException { - when(wrapped.export(deserializedData)).thenReturn(CompletableResultCode.ofSuccess()); - - createDummyFile(); - when(clock.now()).thenReturn(MILLISECONDS.toNanos(1000L + MIN_FILE_AGE_FOR_READ_MILLIS)); - - assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isTrue(); - } - - @Test - void whenExportingStoredBatch_withAvailableData_andUnsuccessfullyProcessed_returnFalse() - throws IOException { - when(wrapped.export(deserializedData)).thenReturn(CompletableResultCode.ofSuccess()); - - createDummyFile(); - when(clock.now()).thenReturn(1000L + MIN_FILE_AGE_FOR_READ_MILLIS); - - assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isFalse(); - } - - @Test - void whenExportingStoredBatch_withNoAvailableData_returnFalse() throws IOException { - assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isFalse(); - } - - @Test - void verifyStorageFolderIsCreated() { - assertThat(new File(rootDir, STORAGE_FOLDER_NAME).exists()).isTrue(); - } - - @Test - void whenDeserializationFails_returnFalse() throws IOException { - doThrow(DeserializationException.class).when(deserializer).deserialize(any()); - - assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isFalse(); - } - - private void createDummyFile() throws IOException { - File file = new File(rootDir, STORAGE_FOLDER_NAME + "/" + 1000L); - Files.write(file.toPath(), singletonList("First line")); - } - - private void setUpSerializer() throws DeserializationException { - deserializer = mock(); - when(deserializer.deserialize(any())).thenReturn(deserializedData); - } - - private static Clock createClockMock() { - Clock mock = mock(); - when(mock.now()).thenReturn(MILLISECONDS.toNanos(1000L)); - return mock; - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java index ee4deb57a..d30466430 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/IntegrationTest.java @@ -5,25 +5,25 @@ package io.opentelemetry.contrib.disk.buffering; -import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static java.lang.Thread.sleep; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; +import static org.mockito.ArgumentMatchers.anyCollection; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; import io.opentelemetry.api.logs.Logger; import io.opentelemetry.api.metrics.Meter; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.Tracer; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterBuilder; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.FromDiskExporterImpl; -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.sdk.common.Clock; -import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.contrib.disk.buffering.exporters.LogRecordToDiskExporter; +import io.opentelemetry.contrib.disk.buffering.exporters.MetricToDiskExporter; +import io.opentelemetry.contrib.disk.buffering.exporters.SpanToDiskExporter; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.ExporterCallback; +import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileLogRecordStorage; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileMetricStorage; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileSpanStorage; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileStorageConfiguration; import io.opentelemetry.sdk.logs.SdkLoggerProvider; import io.opentelemetry.sdk.logs.data.LogRecordData; import io.opentelemetry.sdk.logs.export.LogRecordExporter; @@ -32,159 +32,124 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; -import io.opentelemetry.sdk.testing.exporter.InMemoryLogRecordExporter; -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricExporter; -import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SimpleSpanProcessor; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.File; import java.io.IOException; -import java.util.Collection; -import java.util.concurrent.TimeUnit; -import java.util.function.Function; -import java.util.function.Supplier; -import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.io.TempDir; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) class IntegrationTest { - private InMemorySpanExporter memorySpanExporter; private Tracer tracer; - private InMemoryMetricExporter memoryMetricExporter; private SdkMeterProvider meterProvider; private Meter meter; - private InMemoryLogRecordExporter memoryLogRecordExporter; private Logger logger; - private Clock clock; - @TempDir File rootDir; - private static final long INITIAL_TIME_IN_MILLIS = 1000; - private static final long NOW_NANOS = MILLISECONDS.toNanos(INITIAL_TIME_IN_MILLIS); - private StorageConfiguration storageConfig; - private Storage spanStorage; + private SignalStorage.Span spanStorage; + private SignalStorage.LogRecord logStorage; + private SignalStorage.Metric metricStorage; + private SpanToDiskExporter spanToDiskExporter; + private MetricToDiskExporter metricToDiskExporter; + private LogRecordToDiskExporter logToDiskExporter; + @Mock private ExporterCallback spanCallback; + @Mock private ExporterCallback logCallback; + @Mock private ExporterCallback metricCallback; + @TempDir private File rootDir; + private static final long DELAY_BEFORE_READING_MILLIS = 500; @BeforeEach - void setUp() throws IOException { - clock = mock(); - storageConfig = StorageConfiguration.getDefault(rootDir); - spanStorage = - Storage.builder(SignalTypes.spans) - .setStorageConfiguration(storageConfig) - .setStorageClock(clock) + void setUp() { + FileStorageConfiguration storageConfig = + FileStorageConfiguration.builder() + .setMaxFileAgeForWriteMillis(DELAY_BEFORE_READING_MILLIS - 1) + .setMinFileAgeForReadMillis(DELAY_BEFORE_READING_MILLIS) .build(); - when(clock.now()).thenReturn(NOW_NANOS); - // Setting up spans - memorySpanExporter = InMemorySpanExporter.create(); - ToDiskExporter toDiskSpanExporter = - buildToDiskExporter(SignalSerializer.ofSpans(), memorySpanExporter::export); - SpanToDiskExporter spanToDiskExporter = new SpanToDiskExporter(toDiskSpanExporter); + spanStorage = FileSpanStorage.create(new File(rootDir, "spans"), storageConfig); + spanToDiskExporter = + SpanToDiskExporter.builder(spanStorage).setExporterCallback(spanCallback).build(); tracer = createTracerProvider(spanToDiskExporter).get("SpanInstrumentationScope"); // Setting up metrics - memoryMetricExporter = InMemoryMetricExporter.create(); - ToDiskExporter toDiskMetricExporter = - buildToDiskExporter(SignalSerializer.ofMetrics(), memoryMetricExporter::export); - MetricToDiskExporter metricToDiskExporter = - new MetricToDiskExporter(toDiskMetricExporter, memoryMetricExporter); + metricStorage = FileMetricStorage.create(new File(rootDir, "metrics"), storageConfig); + metricToDiskExporter = + MetricToDiskExporter.builder(metricStorage).setExporterCallback(metricCallback).build(); meterProvider = createMeterProvider(metricToDiskExporter); meter = meterProvider.get("MetricInstrumentationScope"); // Setting up logs - memoryLogRecordExporter = InMemoryLogRecordExporter.create(); - ToDiskExporter toDiskLogExporter = - buildToDiskExporter(SignalSerializer.ofLogs(), memoryLogRecordExporter::export); - LogRecordToDiskExporter logToDiskExporter = new LogRecordToDiskExporter(toDiskLogExporter); + logStorage = FileLogRecordStorage.create(new File(rootDir, "logs"), storageConfig); + logToDiskExporter = + LogRecordToDiskExporter.builder(logStorage).setExporterCallback(logCallback).build(); logger = createLoggerProvider(logToDiskExporter).get("LogInstrumentationScope"); } @AfterEach void tearDown() throws IOException { + // Closing span exporter + spanToDiskExporter.shutdown(); + verify(spanCallback).onShutdown(); + verifyNoMoreInteractions(spanCallback); + + // Closing log exporter + logToDiskExporter.shutdown(); + verify(logCallback).onShutdown(); + verifyNoMoreInteractions(spanCallback); + + // Closing metric exporter + metricToDiskExporter.shutdown(); + verify(metricCallback).onShutdown(); + verifyNoMoreInteractions(spanCallback); + + // Closing storages spanStorage.close(); - } - - @NotNull - private ToDiskExporter buildToDiskExporter( - SignalSerializer serializer, Function, CompletableResultCode> exporter) { - return ToDiskExporter.builder(spanStorage) - .setSerializer(serializer) - .setExportFunction(exporter) - .build(); - } - - @NotNull - private static FromDiskExporterImpl buildFromDiskExporter( - FromDiskExporterBuilder builder, - Function, CompletableResultCode> exportFunction, - SignalDeserializer deserializer) - throws IOException { - return builder.setExportFunction(exportFunction).setDeserializer(deserializer).build(); + logStorage.close(); + metricStorage.close(); } @Test - void verifySpansIntegration() throws IOException { + void verifyIntegration() throws InterruptedException { + // Creating span Span span = tracer.spanBuilder("Span name").startSpan(); span.end(); - FromDiskExporterImpl fromDiskExporter = - buildFromDiskExporter( - FromDiskExporterImpl.builder(spanStorage), - memorySpanExporter::export, - SignalDeserializer.ofSpans()); - assertExporter(fromDiskExporter, () -> memorySpanExporter.getFinishedSpanItems().size()); - } + verify(spanCallback).onExportSuccess(anyCollection()); + verifyNoMoreInteractions(spanCallback); - @Test - void verifyMetricsIntegration() throws IOException { - meter.counterBuilder("Counter").build().add(2); - meterProvider.forceFlush(); - - FromDiskExporterImpl fromDiskExporter = - buildFromDiskExporter( - FromDiskExporterImpl.builder(spanStorage), - memoryMetricExporter::export, - SignalDeserializer.ofMetrics()); - assertExporter(fromDiskExporter, () -> memoryMetricExporter.getFinishedMetricItems().size()); - } + // Creating log + logger.logRecordBuilder().setBody("Log body").emit(); + verify(logCallback).onExportSuccess(anyCollection()); + verifyNoMoreInteractions(spanCallback); - @Test - void verifyLogRecordsIntegration() throws IOException { - logger.logRecordBuilder().setBody("I'm a log!").emit(); - - FromDiskExporterImpl fromDiskExporter = - buildFromDiskExporter( - FromDiskExporterImpl.builder(spanStorage), - memoryLogRecordExporter::export, - SignalDeserializer.ofLogs()); - assertExporter( - fromDiskExporter, () -> memoryLogRecordExporter.getFinishedLogRecordItems().size()); - } - - private void assertExporter(FromDiskExporterImpl exporter, Supplier finishedItems) - throws IOException { - // Verify no data has been received in the original exporter until this point. - assertThat(finishedItems.get()).isEqualTo(0); - - // Go to the future when we can read the stored items. - fastForwardTimeByMillis(storageConfig.getMinFileAgeForReadMillis()); - - // Read and send stored data. - assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isTrue(); - - // Now the data must have been delegated to the original exporter. - assertThat(finishedItems.get()).isEqualTo(1); - - // Bonus: Try to read again, no more data should be available. - assertThat(exporter.exportStoredBatch(1, TimeUnit.SECONDS)).isFalse(); - assertThat(finishedItems.get()).isEqualTo(1); - } - - @SuppressWarnings("DirectInvocationOnMock") - private void fastForwardTimeByMillis(long milliseconds) { - when(clock.now()).thenReturn(NOW_NANOS + MILLISECONDS.toNanos(milliseconds)); + // Creating metric + meter.counterBuilder("counter").build().add(1); + meterProvider.forceFlush(); + verify(metricCallback).onExportSuccess(anyCollection()); + verifyNoMoreInteractions(spanCallback); + + // Waiting for read time + sleep(DELAY_BEFORE_READING_MILLIS); + + // Read + List storedSpans = new ArrayList<>(); + List storedLogs = new ArrayList<>(); + List storedMetrics = new ArrayList<>(); + spanStorage.forEach(storedSpans::addAll); + logStorage.forEach(storedLogs::addAll); + metricStorage.forEach(storedMetrics::addAll); + + assertThat(storedSpans).hasSize(1); + assertThat(storedLogs).hasSize(1); + assertThat(storedMetrics).hasSize(1); } private static SdkTracerProvider createTracerProvider(SpanExporter exporter) { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporterTest.java deleted file mode 100644 index 6409cf067..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/LogRecordToDiskExporterTest.java +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.logs.data.LogRecordData; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class LogRecordToDiskExporterTest { - - @Mock private ToDiskExporter delegate; - - @Test - void delegateShutdown_success() throws IOException { - LogRecordToDiskExporter testClass = new LogRecordToDiskExporter(delegate); - CompletableResultCode result = testClass.shutdown(); - assertThat(result.isSuccess()).isTrue(); - verify(delegate).shutdown(); - } - - @Test - void delegateShutdown_fail() throws IOException { - doThrow(new IOException("boom")).when(delegate).shutdown(); - LogRecordToDiskExporter testClass = new LogRecordToDiskExporter(delegate); - CompletableResultCode result = testClass.shutdown(); - assertThat(result.isSuccess()).isFalse(); - verify(delegate).shutdown(); - } - - @Test - void delegateExport() { - LogRecordData log1 = mock(); - LogRecordData log2 = mock(); - List logRecords = Arrays.asList(log1, log2); - - LogRecordToDiskExporter testClass = new LogRecordToDiskExporter(delegate); - testClass.export(logRecords); - - verify(delegate).export(logRecords); - } - - @Test - void flushReturnsSuccess() { - LogRecordToDiskExporter testClass = new LogRecordToDiskExporter(delegate); - CompletableResultCode result = testClass.flush(); - assertThat(result.isSuccess()).isTrue(); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporterTest.java deleted file mode 100644 index 9ba84f67c..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/MetricToDiskExporterTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import static io.opentelemetry.sdk.metrics.data.AggregationTemporality.CUMULATIVE; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.metrics.InstrumentType; -import io.opentelemetry.sdk.metrics.data.AggregationTemporality; -import io.opentelemetry.sdk.metrics.data.MetricData; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class MetricToDiskExporterTest { - - @Mock private ToDiskExporter delegate; - - @Test - void delegateShutdown_success() throws IOException { - MetricToDiskExporter testClass = - new MetricToDiskExporter(delegate, MetricToDiskExporterTest::temporalityFn); - CompletableResultCode result = testClass.shutdown(); - assertThat(result.isSuccess()).isTrue(); - verify(delegate).shutdown(); - } - - private static AggregationTemporality temporalityFn(InstrumentType instrumentType) { - return CUMULATIVE; - } - - @Test - void delegateShutdown_fail() throws IOException { - doThrow(new IOException("boom")).when(delegate).shutdown(); - MetricToDiskExporter testClass = - new MetricToDiskExporter(delegate, MetricToDiskExporterTest::temporalityFn); - CompletableResultCode result = testClass.shutdown(); - assertThat(result.isSuccess()).isFalse(); - verify(delegate).shutdown(); - } - - @Test - void delegateExport() { - MetricData metric1 = mock(); - MetricData metric2 = mock(); - List metrics = Arrays.asList(metric1, metric2); - - MetricToDiskExporter testClass = - new MetricToDiskExporter(delegate, MetricToDiskExporterTest::temporalityFn); - - testClass.export(metrics); - - verify(delegate).export(metrics); - } - - @Test - void flushReturnsSuccess() { - MetricToDiskExporter testClass = - new MetricToDiskExporter(delegate, MetricToDiskExporterTest::temporalityFn); - - CompletableResultCode result = testClass.flush(); - assertThat(result.isSuccess()).isTrue(); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java deleted file mode 100644 index 2ea0d2b8a..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanFromDiskExporterTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import io.opentelemetry.api.common.AttributeKey; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.trace.SpanContext; -import io.opentelemetry.api.trace.SpanKind; -import io.opentelemetry.api.trace.StatusCode; -import io.opentelemetry.api.trace.TraceFlags; -import io.opentelemetry.api.trace.TraceState; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.spans.models.SpanDataImpl; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.contrib.disk.buffering.testutils.TestData; -import io.opentelemetry.sdk.common.Clock; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.sdk.trace.data.SpanData; -import io.opentelemetry.sdk.trace.data.StatusData; -import io.opentelemetry.sdk.trace.export.SpanExporter; -import java.io.File; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.TimeUnit; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; -import org.mockito.ArgumentCaptor; - -class SpanFromDiskExporterTest { - - @TempDir File tempDir; - - @SuppressWarnings("unchecked") - @Test - void fromDisk() throws Exception { - Clock clock = mock(Clock.class); - long start = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()); - when(clock.now()).thenReturn(start); - Storage storage = - Storage.builder(SignalTypes.spans) - .setStorageConfiguration(StorageConfiguration.builder().setRootDir(tempDir).build()) - .setStorageClock(clock) - .build(); - - List spans = writeSomeSpans(storage); - - when(clock.now()).thenReturn(start + TimeUnit.SECONDS.toNanos(60)); - - SpanExporter exporter = mock(); - ArgumentCaptor> capture = ArgumentCaptor.forClass(Collection.class); - when(exporter.export(capture.capture())).thenReturn(CompletableResultCode.ofSuccess()); - - SpanFromDiskExporter testClass = SpanFromDiskExporter.create(exporter, storage); - boolean result = testClass.exportStoredBatch(30, TimeUnit.SECONDS); - assertThat(result).isTrue(); - List exportedSpans = (List) capture.getValue(); - - long now = spans.get(0).getStartEpochNanos(); - SpanData expected1 = makeSpan1(TraceFlags.getSampled(), now); - SpanData expected2 = makeSpan2(TraceFlags.getSampled(), now); - - assertThat(exportedSpans.get(0)).isEqualTo(expected1); - assertThat(exportedSpans.get(1)).isEqualTo(expected2); - assertThat(exportedSpans).containsExactly(expected1, expected2); - - verify(exporter).export(eq(Arrays.asList(expected1, expected2))); - } - - private static List writeSomeSpans(Storage storage) throws Exception { - long now = System.currentTimeMillis() * 1_000_000; - SpanData span1 = makeSpan1(TraceFlags.getDefault(), now); - SpanData span2 = makeSpan2(TraceFlags.getSampled(), now); - List spans = Arrays.asList(span1, span2); - - storage.write(SignalSerializer.ofSpans().initialize(spans)); - storage.flush(); - return spans; - } - - private static SpanData makeSpan1(TraceFlags parentSpanContextFlags, long now) { - Attributes attributes = Attributes.of(AttributeKey.stringKey("foo"), "bar"); - SpanContext parentContext = TestData.makeContext(parentSpanContextFlags, TestData.SPAN_ID); - return SpanDataImpl.builder() - .setName("span1") - .setSpanContext( - SpanContext.create( - TestData.TRACE_ID, - TestData.SPAN_ID, - TraceFlags.getDefault(), - TraceState.getDefault())) - .setParentSpanContext(parentContext) - .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) - .setStatus(StatusData.create(StatusCode.OK, "whatever")) - .setAttributes(attributes) - .setKind(SpanKind.SERVER) - .setStartEpochNanos(now) - .setEndEpochNanos(now + 50_000_000) - .setTotalRecordedEvents(0) - .setTotalRecordedLinks(0) - .setTotalAttributeCount(attributes.size()) - .setLinks(Collections.emptyList()) - .setEvents(Collections.emptyList()) - .setResource(Resource.getDefault()) - .build(); - } - - private static SpanData makeSpan2(TraceFlags parentSpanContextFlags, long now) { - Attributes attributes = Attributes.of(AttributeKey.stringKey("bar"), "baz"); - String spanId = "aaaaaaaaa12312312"; - SpanContext parentContext = TestData.makeContext(parentSpanContextFlags, spanId); - return SpanDataImpl.builder() - .setName("span2") - .setSpanContext( - SpanContext.create( - TestData.TRACE_ID, spanId, TraceFlags.getSampled(), TraceState.getDefault())) - .setParentSpanContext(parentContext) - .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) - .setStatus(StatusData.create(StatusCode.OK, "excellent")) - .setAttributes(attributes) - .setKind(SpanKind.CLIENT) - .setStartEpochNanos(now + 12) - .setEndEpochNanos(now + 12 + 40_000_000) - .setTotalRecordedEvents(0) - .setTotalRecordedLinks(0) - .setTotalAttributeCount(attributes.size()) - .setLinks(Collections.emptyList()) - .setEvents(Collections.emptyList()) - .setResource(Resource.getDefault()) - .build(); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporterTest.java deleted file mode 100644 index 96dcfcaa9..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/SpanToDiskExporterTest.java +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; - -import io.opentelemetry.contrib.disk.buffering.internal.exporter.ToDiskExporter; -import io.opentelemetry.sdk.common.CompletableResultCode; -import io.opentelemetry.sdk.trace.data.SpanData; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class SpanToDiskExporterTest { - @Mock private ToDiskExporter delegate; - - @Test - void delegateShutdown_success() throws IOException { - SpanToDiskExporter testClass = new SpanToDiskExporter(delegate); - CompletableResultCode result = testClass.shutdown(); - assertThat(result.isSuccess()).isTrue(); - verify(delegate).shutdown(); - } - - @Test - void delegateShutdown_fail() throws IOException { - doThrow(new IOException("boom")).when(delegate).shutdown(); - SpanToDiskExporter testClass = new SpanToDiskExporter(delegate); - CompletableResultCode result = testClass.shutdown(); - assertThat(result.isSuccess()).isFalse(); - verify(delegate).shutdown(); - } - - @Test - void delegateExport() { - SpanData span1 = mock(); - SpanData span2 = mock(); - List spans = Arrays.asList(span1, span2); - - SpanToDiskExporter testClass = new SpanToDiskExporter(delegate); - testClass.export(spans); - - verify(delegate).export(spans); - } - - @Test - void flushReturnsSuccess() { - SpanToDiskExporter testClass = new SpanToDiskExporter(delegate); - CompletableResultCode result = testClass.flush(); - assertThat(result.isSuccess()).isTrue(); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java deleted file mode 100644 index 0a98061ac..000000000 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporter/ToDiskExporterTest.java +++ /dev/null @@ -1,90 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.contrib.disk.buffering.internal.exporter; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.Storage; -import io.opentelemetry.sdk.common.CompletableResultCode; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.Function; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class ToDiskExporterTest { - - private final List records = Arrays.asList("one", "two", "three"); - - @Mock private SignalSerializer serializer; - - @Mock private Storage storage; - private ToDiskExporter toDiskExporter; - private Function, CompletableResultCode> exportFn; - private Collection exportedFnSeen; - private AtomicReference exportFnResultToReturn; - - @BeforeEach - void setup() { - exportedFnSeen = null; - exportFnResultToReturn = new AtomicReference<>(null); - exportFn = - (Collection x) -> { - exportedFnSeen = x; - return exportFnResultToReturn.get(); - }; - toDiskExporter = new ToDiskExporter<>(serializer, exportFn, storage); - } - - @Test - void whenWritingSucceedsOnExport_returnSuccessfulResultCode() throws Exception { - when(storage.write(any())).thenReturn(true); - CompletableResultCode completableResultCode = toDiskExporter.export(records); - assertThat(completableResultCode.isSuccess()).isTrue(); - verify(storage).write(any()); - assertThat(exportedFnSeen).isNull(); - } - - @Test - void whenWritingFailsOnExport_doExportRightAway() throws Exception { - when(storage.write(any())).thenReturn(false); - exportFnResultToReturn.set(CompletableResultCode.ofSuccess()); - - CompletableResultCode completableResultCode = toDiskExporter.export(records); - - assertThat(completableResultCode.isSuccess()).isTrue(); - assertThat(exportedFnSeen).isEqualTo(records); - } - - @Test - void whenExceptionInWrite_doExportRightAway() throws Exception { - when(storage.write(any())).thenThrow(new IOException("boom")); - exportFnResultToReturn.set(CompletableResultCode.ofFailure()); - - CompletableResultCode completableResultCode = toDiskExporter.export(records); - - assertThat(completableResultCode.isSuccess()).isFalse(); - assertThat(exportedFnSeen).isEqualTo(records); - } - - @Test - void shutdownClosesStorage() throws Exception { - toDiskExporter.export(records); - toDiskExporter.shutdown(); - verify(storage).close(); - } -} diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporterTest.java similarity index 78% rename from disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java rename to disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporterTest.java index 6f7db99ef..30bfe310a 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/exporters/SignalStorageExporterTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/exporters/SignalStorageExporterTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package io.opentelemetry.contrib.disk.buffering.exporters; +package io.opentelemetry.contrib.disk.buffering.internal.exporters; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.anyCollection; @@ -13,7 +13,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -import io.opentelemetry.contrib.disk.buffering.SignalType; +import io.opentelemetry.contrib.disk.buffering.exporters.callback.ExporterCallback; import io.opentelemetry.contrib.disk.buffering.storage.SignalStorage; import io.opentelemetry.contrib.disk.buffering.storage.result.WriteResult; import io.opentelemetry.sdk.common.CompletableResultCode; @@ -32,32 +32,34 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +@SuppressWarnings("unchecked") @ExtendWith(MockitoExtension.class) class SignalStorageExporterTest { - @Mock private ExporterCallback callback; + @Mock private ExporterCallback callback; @Test void verifyExportToStorage_success() { SignalStorage.Span storage = new TestSpanStorage(); - SignalType signalType = SignalType.SPAN; SignalStorageExporter storageExporter = - new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1), signalType); + new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1)); SpanData item1 = mock(); SpanData item2 = mock(); SpanData item3 = mock(); - CompletableResultCode resultCode = storageExporter.exportToStorage(Arrays.asList(item1, item2)); + List items = Arrays.asList(item1, item2); + CompletableResultCode resultCode = storageExporter.exportToStorage(items); assertThat(resultCode.isSuccess()).isTrue(); - verify(callback).onExportSuccess(signalType); + verify(callback).onExportSuccess(items); verifyNoMoreInteractions(callback); // Adding more items clearInvocations(callback); - resultCode = storageExporter.exportToStorage(Collections.singletonList(item3)); + List items2 = Collections.singletonList(item3); + resultCode = storageExporter.exportToStorage(items2); assertThat(resultCode.isSuccess()).isTrue(); - verify(callback).onExportSuccess(signalType); + verify(callback).onExportSuccess(items2); verifyNoMoreInteractions(callback); // Checking items @@ -68,38 +70,36 @@ void verifyExportToStorage_success() { assertThat(storedItems).containsExactly(item1, item2, item3); } - @SuppressWarnings("ThrowableNotThrown") @Test void verifyExportToStorage_failure() { SignalStorage.Span storage = mock(); - SignalType signalType = SignalType.SPAN; SignalStorageExporter storageExporter = - new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1), signalType); + new SignalStorageExporter<>(storage, callback, Duration.ofSeconds(1)); SpanData item1 = mock(); // Without exception when(storage.write(anyCollection())) - .thenReturn(CompletableFuture.completedFuture(WriteResult.create(false, null))); + .thenReturn(CompletableFuture.completedFuture(WriteResult.error(null))); - CompletableResultCode resultCode = - storageExporter.exportToStorage(Collections.singletonList(item1)); + List items = Collections.singletonList(item1); + CompletableResultCode resultCode = storageExporter.exportToStorage(items); assertThat(resultCode.isSuccess()).isFalse(); assertThat(resultCode.getFailureThrowable()).isNull(); - verify(callback).onExportError(signalType, null); + verify(callback).onExportError(items, null); verifyNoMoreInteractions(callback); // With exception clearInvocations(callback); Exception exception = new Exception(); when(storage.write(anyCollection())) - .thenReturn(CompletableFuture.completedFuture(WriteResult.create(false, exception))); + .thenReturn(CompletableFuture.completedFuture(WriteResult.error(exception))); - resultCode = storageExporter.exportToStorage(Collections.singletonList(item1)); + resultCode = storageExporter.exportToStorage(items); assertThat(resultCode.isSuccess()).isFalse(); assertThat(resultCode.getFailureThrowable()).isEqualTo(exception); - verify(callback).onExportError(signalType, exception); + verify(callback).onExportError(items, exception); verifyNoMoreInteractions(callback); } @@ -129,7 +129,7 @@ public Iterator> iterator() { @Nonnull private static CompletableFuture getSuccessfulFuture() { - return CompletableFuture.completedFuture(WriteResult.create(true, null)); + return CompletableFuture.completedFuture(WriteResult.successful()); } } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java index d8a95a9cc..044e7be9b 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/FolderManagerTest.java @@ -35,7 +35,7 @@ class FolderManagerTest { @BeforeEach void setUp() { clock = mock(); - folderManager = new FolderManager(rootDir, TestData.getConfiguration(rootDir), clock); + folderManager = new FolderManager(rootDir, TestData.getConfiguration(), clock); } @AfterEach @@ -51,6 +51,19 @@ void createWritableFile_withTimeMillisAsName() throws IOException { assertThat(file.getFile().getName()).isEqualTo("1000"); } + @Test + void clearFiles() throws IOException { + when(clock.now()).thenReturn(MILLISECONDS.toNanos(1000L)); + + // Creating file + folderManager.createWritableFile(); + assertThat(rootDir.list()).containsExactly("1000"); + + // Clear + folderManager.clear(); + assertThat(rootDir.list()).isEmpty(); + } + @Test void createWritableFile_andRemoveOldestOne_whenTheAvailableFolderSpaceIsNotEnough() throws IOException { diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java index b33387a8c..611506b1b 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/StorageTest.java @@ -5,47 +5,51 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage; -import static io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult.TRY_LATER; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.FIRST_LOG_RECORD; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_AGE_FOR_READ_MILLIS; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_AGE_FOR_WRITE_MILLIS; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MIN_FILE_AGE_FOR_READ_MILLIS; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.SECOND_LOG_RECORD; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.THIRD_LOG_RECORD; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.getConfiguration; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyNoInteractions; -import static org.mockito.Mockito.when; - -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.ByteArraySerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.ReadableFile; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.WritableFile; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.fail; + +import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; -import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.WritableResult; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; +import io.opentelemetry.sdk.common.Clock; +import io.opentelemetry.sdk.logs.data.LogRecordData; import java.io.File; import java.io.IOException; -import java.util.function.Function; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; -@SuppressWarnings("unchecked") class StorageTest { + @TempDir private File destinationDir; private FolderManager folderManager; - private Storage storage; - private Function processing; - private ReadableFile readableFile; - private WritableFile writableFile; + private Storage storage; + private SignalSerializer serializer; + private AtomicLong currentTimeMillis; + private static final SignalDeserializer DESERIALIZER = SignalDeserializer.ofLogs(); @BeforeEach - void setUp() throws IOException { - folderManager = mock(); - readableFile = mock(); - writableFile = createWritableFile(); - processing = mock(); - when(readableFile.readAndProcess(processing)).thenReturn(ReadableResult.SUCCEEDED); - storage = new Storage(folderManager, true); + void setUp() { + currentTimeMillis = new AtomicLong(0); + serializer = SignalSerializer.ofLogs(); + folderManager = FolderManager.create(destinationDir, getConfiguration(), new TestClock()); + storage = new Storage<>(folderManager); } @AfterEach @@ -54,203 +58,167 @@ void tearDown() throws IOException { } @Test - void whenReadingAndProcessingSuccessfully_returnSuccess() throws IOException { - when(folderManager.getReadableFile()).thenReturn(readableFile); - - assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.SUCCEEDED); + void writeAndRead() throws IOException { + assertThat(write(Arrays.asList(FIRST_LOG_RECORD, SECOND_LOG_RECORD))).isTrue(); + assertThat(write(Collections.singletonList(THIRD_LOG_RECORD))).isTrue(); + assertThat(destinationDir.list()).hasSize(1); + forwardToReadTime(); - verify(readableFile).readAndProcess(processing); - } - - @Test - void whenReadableFileProcessingFails_returnTryLater() throws IOException { - when(folderManager.getReadableFile()).thenReturn(readableFile); - when(readableFile.readAndProcess(processing)).thenReturn(TRY_LATER); + ReadableResult readResult = storage.readNext(DESERIALIZER); + assertNotNull(readResult); + assertThat(readResult.getContent()).containsExactly(FIRST_LOG_RECORD, SECOND_LOG_RECORD); + assertThat(destinationDir.list()).hasSize(1); - assertThat(storage.readAndProcess(processing)).isEqualTo(TRY_LATER); - - verify(readableFile).readAndProcess(processing); - } - - @Test - void whenReadingMultipleTimes_reuseReader() throws IOException { - ReadableFile anotherReadable = mock(); - when(folderManager.getReadableFile()).thenReturn(readableFile).thenReturn(anotherReadable); + // Delete result and read again + readResult.delete(); + readResult.close(); + ReadableResult readResult2 = storage.readNext(DESERIALIZER); + assertNotNull(readResult2); + assertThat(readResult2.getContent()).containsExactly(THIRD_LOG_RECORD); + assertThat(destinationDir.list()).hasSize(1); - assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.SUCCEEDED); - assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.SUCCEEDED); + // Read again without closing previous result + try { + storage.readNext(DESERIALIZER); + fail(); + } catch (IllegalStateException e) { + assertThat(e) + .hasMessage("You must close any previous ReadableResult before requesting a new one"); + } - verify(readableFile, times(2)).readAndProcess(processing); - verify(folderManager, times(1)).getReadableFile(); - verifyNoInteractions(anotherReadable); + // Read again when no more data is available (delete file) + readResult2.close(); + assertNull(storage.readNext(DESERIALIZER)); + assertThat(destinationDir.list()).isEmpty(); } @Test - void whenWritingMultipleTimes_reuseWriter() throws IOException { - ByteArraySerializer data = new ByteArraySerializer(new byte[1]); - WritableFile anotherWriter = createWritableFile(); - when(folderManager.createWritableFile()).thenReturn(writableFile).thenReturn(anotherWriter); - - storage.write(data); - storage.write(data); - - verify(writableFile, times(2)).append(data); - verify(folderManager, times(1)).createWritableFile(); - verifyNoInteractions(anotherWriter); - } - - @Test - void whenAttemptingToReadAfterClosed_returnFailed() throws IOException { + void interactionAfterClosed() throws IOException { + assertThat(write(Arrays.asList(FIRST_LOG_RECORD, SECOND_LOG_RECORD))).isTrue(); storage.close(); - assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.FAILED); - } + assertThat(destinationDir.list()).hasSize(1); + forwardToReadTime(); - @Test - void whenAttemptingToWriteAfterClosed_returnFalse() throws IOException { - storage.close(); - assertThat(storage.write(new ByteArraySerializer(new byte[1]))).isFalse(); - } + // Reading + assertNull(storage.readNext(DESERIALIZER)); - @Test - void whenNoFileAvailableForReading_returnFailed() throws IOException { - assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.FAILED); + // Writing + assertThat(write(Collections.singletonList(THIRD_LOG_RECORD))).isFalse(); } @Test void whenTheReadTimeExpires_lookForNewFileToRead() throws IOException { - when(folderManager.getReadableFile()).thenReturn(readableFile).thenReturn(null); - when(readableFile.readAndProcess(processing)).thenReturn(ReadableResult.FAILED); + long firstFileWriteTime = 1000; + long secondFileWriteTime = firstFileWriteTime + MAX_FILE_AGE_FOR_WRITE_MILLIS + 1; + currentTimeMillis.set(firstFileWriteTime); + assertThat(write(Arrays.asList(FIRST_LOG_RECORD, SECOND_LOG_RECORD))).isTrue(); - storage.readAndProcess(processing); + // Forward past first file write time + currentTimeMillis.set(secondFileWriteTime); + assertThat(write(Collections.singletonList(THIRD_LOG_RECORD))).isTrue(); + assertThat(destinationDir.list()) + .containsExactlyInAnyOrder( + String.valueOf(firstFileWriteTime), String.valueOf(secondFileWriteTime)); - verify(folderManager, times(2)).getReadableFile(); - } + // Forward past first time read + currentTimeMillis.set(firstFileWriteTime + MAX_FILE_AGE_FOR_READ_MILLIS + 1); - @Test - void whenNoMoreLinesToRead_lookForNewFileToRead() throws IOException { - when(folderManager.getReadableFile()).thenReturn(readableFile).thenReturn(null); - when(readableFile.readAndProcess(processing)).thenReturn(ReadableResult.FAILED); + // Read + ReadableResult result = storage.readNext(DESERIALIZER); + assertNotNull(result); + assertThat(result.getContent()).containsExactly(THIRD_LOG_RECORD); + assertThat(destinationDir.list()) + .containsExactlyInAnyOrder( + String.valueOf(firstFileWriteTime), String.valueOf(secondFileWriteTime)); - storage.readAndProcess(processing); - - verify(folderManager, times(2)).getReadableFile(); + // Purge expired files on write + currentTimeMillis.set(50000); + assertThat(write(Collections.singletonList(FIRST_LOG_RECORD))).isTrue(); + assertThat(destinationDir.list()).containsExactly("50000"); } @Test - void whenResourceClosed_lookForNewFileToRead() throws IOException { - when(folderManager.getReadableFile()).thenReturn(readableFile).thenReturn(null); - when(readableFile.readAndProcess(processing)).thenReturn(ReadableResult.FAILED); - - storage.readAndProcess(processing); - - verify(folderManager, times(2)).getReadableFile(); - } - - @Test - void whenEveryNewFileFoundCannotBeRead_returnContentNotAvailable() throws IOException { - when(folderManager.getReadableFile()).thenReturn(readableFile); - when(readableFile.readAndProcess(processing)).thenReturn(ReadableResult.FAILED); - - assertThat(storage.readAndProcess(processing)).isEqualTo(ReadableResult.FAILED); - - verify(folderManager, times(3)).getReadableFile(); - } - - @Test - void appendDataToFile() throws IOException { - when(folderManager.createWritableFile()).thenReturn(writableFile); - ByteArraySerializer data = new ByteArraySerializer(new byte[1]); - - storage.write(data); - - verify(writableFile).append(data); - } - - @Test - void whenWritingTimeoutHappens_retryWithNewFile() throws IOException { - ByteArraySerializer data = new ByteArraySerializer(new byte[1]); - WritableFile workingWritableFile = createWritableFile(); - when(folderManager.createWritableFile()) - .thenReturn(writableFile) - .thenReturn(workingWritableFile); - when(writableFile.append(data)).thenReturn(WritableResult.FAILED); - - storage.write(data); - - verify(folderManager, times(2)).createWritableFile(); - } - - @Test - void whenThereIsNoSpaceAvailableForWriting_retryWithNewFile() throws IOException { - ByteArraySerializer data = new ByteArraySerializer(new byte[1]); - WritableFile workingWritableFile = createWritableFile(); - when(folderManager.createWritableFile()) - .thenReturn(writableFile) - .thenReturn(workingWritableFile); - when(writableFile.append(data)).thenReturn(WritableResult.FAILED); - - storage.write(data); - - verify(folderManager, times(2)).createWritableFile(); - } - - @Test - void whenWritingResourceIsClosed_retryWithNewFile() throws IOException { - ByteArraySerializer data = new ByteArraySerializer(new byte[1]); - WritableFile workingWritableFile = createWritableFile(); - when(folderManager.createWritableFile()) - .thenReturn(writableFile) - .thenReturn(workingWritableFile); - when(writableFile.append(data)).thenReturn(WritableResult.FAILED); - - storage.write(data); - - verify(folderManager, times(2)).createWritableFile(); - } - - @Test - void whenEveryAttemptToWriteFails_returnFalse() throws IOException { - ByteArraySerializer data = new ByteArraySerializer(new byte[1]); - when(folderManager.createWritableFile()).thenReturn(writableFile); - when(writableFile.append(data)).thenReturn(WritableResult.FAILED); - - assertThat(storage.write(data)).isFalse(); - - verify(folderManager, times(3)).createWritableFile(); - } - - @Test - void whenClosing_closeWriterAndReaderIfNotNull() throws IOException { - when(folderManager.createWritableFile()).thenReturn(writableFile); - when(folderManager.getReadableFile()).thenReturn(readableFile); - storage.write(new ByteArraySerializer(new byte[1])); - storage.readAndProcess(processing); - - storage.close(); - - verify(writableFile).close(); - verify(readableFile).close(); - } - - @Test - void whenMinFileReadIsNotGraterThanMaxFileWrite_throwException() { - StorageConfiguration invalidConfig = - StorageConfiguration.builder() - .setMaxFileAgeForWriteMillis(2) - .setMinFileAgeForReadMillis(1) - .setRootDir(new File(".")) - .build(); - - assertThatThrownBy( - () -> Storage.builder(SignalTypes.logs).setStorageConfiguration(invalidConfig)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage( - "The configured max file age for writing must be lower than the configured min file age for reading"); - } - - private static WritableFile createWritableFile() throws IOException { - WritableFile mock = mock(); - when(mock.append(any())).thenReturn(WritableResult.SUCCEEDED); - return mock; + void whenNoMoreLinesToRead_lookForNewFileToRead() throws IOException { + long firstFileWriteTime = 1000; + long secondFileWriteTime = firstFileWriteTime + MAX_FILE_AGE_FOR_WRITE_MILLIS + 1; + currentTimeMillis.set(firstFileWriteTime); + assertThat(write(Arrays.asList(FIRST_LOG_RECORD, SECOND_LOG_RECORD))).isTrue(); + + // Forward past first file write time + currentTimeMillis.set(secondFileWriteTime); + assertThat(write(Collections.singletonList(THIRD_LOG_RECORD))).isTrue(); + assertThat(destinationDir.list()) + .containsExactlyInAnyOrder( + String.valueOf(firstFileWriteTime), String.valueOf(secondFileWriteTime)); + + // Forward to all files read time + currentTimeMillis.set(secondFileWriteTime + MIN_FILE_AGE_FOR_READ_MILLIS); + + // Read + ReadableResult result = storage.readNext(DESERIALIZER); + assertNotNull(result); + assertThat(result.getContent()).containsExactly(FIRST_LOG_RECORD, SECOND_LOG_RECORD); + assertThat(destinationDir.list()) + .containsExactlyInAnyOrder( + String.valueOf(firstFileWriteTime), String.valueOf(secondFileWriteTime)); + result.delete(); + result.close(); + + // Read again + ReadableResult result2 = storage.readNext(DESERIALIZER); + assertNotNull(result2); + assertThat(result2.getContent()).containsExactly(THIRD_LOG_RECORD); + assertThat(destinationDir.list()).containsExactly(String.valueOf(secondFileWriteTime)); + result2.close(); + } + + @Test + void deleteFilesWithCorruptedData() throws IOException { + // Add files with invalid data + Files.write( + new File(destinationDir, "1000").toPath(), "random data".getBytes(StandardCharsets.UTF_8)); + Files.write( + new File(destinationDir, "2000").toPath(), "random data".getBytes(StandardCharsets.UTF_8)); + Files.write( + new File(destinationDir, "3000").toPath(), "random data".getBytes(StandardCharsets.UTF_8)); + Files.write( + new File(destinationDir, "4000").toPath(), "random data".getBytes(StandardCharsets.UTF_8)); + + // Set time ready to read all files + currentTimeMillis.set(4000 + MIN_FILE_AGE_FOR_READ_MILLIS); + + // Read + assertNull(storage.readNext(DESERIALIZER)); + assertThat(destinationDir.list()).containsExactly("4000"); // it tries 3 times max per call. + } + + private void forwardToReadTime() { + forwardCurrentTimeByMillis(MIN_FILE_AGE_FOR_READ_MILLIS); + } + + private void forwardCurrentTimeByMillis(long millis) { + currentTimeMillis.set(currentTimeMillis.get() + millis); + } + + private boolean write(Collection items) throws IOException { + serializer.initialize(items); + try { + return storage.write(serializer); + } finally { + serializer.reset(); + } + } + + private class TestClock implements Clock { + + @Override + public long now() { + return TimeUnit.MILLISECONDS.toNanos(currentTimeMillis.get()); + } + + @Override + public long nanoTime() { + return 0; + } } } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java index 75d86726e..3b51125b6 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/TestData.java @@ -5,31 +5,73 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage; -import io.opentelemetry.contrib.disk.buffering.config.StorageConfiguration; -import io.opentelemetry.contrib.disk.buffering.internal.utils.SignalTypes; -import io.opentelemetry.sdk.common.Clock; -import java.io.File; -import java.io.IOException; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.api.logs.Severity; +import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.models.LogRecordDataImpl; +import io.opentelemetry.contrib.disk.buffering.storage.impl.FileStorageConfiguration; +import io.opentelemetry.sdk.logs.data.LogRecordData; public final class TestData { + public static final LogRecordData FIRST_LOG_RECORD = + LogRecordDataImpl.builder() + .setResource(io.opentelemetry.contrib.disk.buffering.testutils.TestData.RESOURCE_FULL) + .setSpanContext(io.opentelemetry.contrib.disk.buffering.testutils.TestData.SPAN_CONTEXT) + .setInstrumentationScopeInfo( + io.opentelemetry.contrib.disk.buffering.testutils.TestData + .INSTRUMENTATION_SCOPE_INFO_FULL) + .setAttributes(io.opentelemetry.contrib.disk.buffering.testutils.TestData.ATTRIBUTES) + .setBodyValue(Value.of("First log body")) + .setSeverity(Severity.DEBUG) + .setSeverityText("Log severity text") + .setTimestampEpochNanos(100L) + .setObservedTimestampEpochNanos(200L) + .setTotalAttributeCount(3) + .setEventName("") + .build(); + + public static final LogRecordData SECOND_LOG_RECORD = + LogRecordDataImpl.builder() + .setResource(io.opentelemetry.contrib.disk.buffering.testutils.TestData.RESOURCE_FULL) + .setSpanContext(io.opentelemetry.contrib.disk.buffering.testutils.TestData.SPAN_CONTEXT) + .setInstrumentationScopeInfo( + io.opentelemetry.contrib.disk.buffering.testutils.TestData + .INSTRUMENTATION_SCOPE_INFO_FULL) + .setAttributes(io.opentelemetry.contrib.disk.buffering.testutils.TestData.ATTRIBUTES) + .setBodyValue(Value.of("Second log body")) + .setSeverity(Severity.DEBUG) + .setSeverityText("Log severity text") + .setTimestampEpochNanos(100L) + .setObservedTimestampEpochNanos(200L) + .setTotalAttributeCount(3) + .setEventName("event") + .build(); + + public static final LogRecordData THIRD_LOG_RECORD = + LogRecordDataImpl.builder() + .setResource(io.opentelemetry.contrib.disk.buffering.testutils.TestData.RESOURCE_FULL) + .setSpanContext(io.opentelemetry.contrib.disk.buffering.testutils.TestData.SPAN_CONTEXT) + .setInstrumentationScopeInfo( + io.opentelemetry.contrib.disk.buffering.testutils.TestData + .INSTRUMENTATION_SCOPE_INFO_FULL) + .setAttributes(io.opentelemetry.contrib.disk.buffering.testutils.TestData.ATTRIBUTES) + .setBodyValue(Value.of("Third log body")) + .setSeverity(Severity.DEBUG) + .setSeverityText("Log severity text") + .setTimestampEpochNanos(100L) + .setObservedTimestampEpochNanos(200L) + .setTotalAttributeCount(3) + .setEventName("") + .build(); + public static final long MAX_FILE_AGE_FOR_WRITE_MILLIS = 1000; public static final long MIN_FILE_AGE_FOR_READ_MILLIS = MAX_FILE_AGE_FOR_WRITE_MILLIS + 500; public static final long MAX_FILE_AGE_FOR_READ_MILLIS = 10_000; - public static final int MAX_FILE_SIZE = 100; - public static final int MAX_FOLDER_SIZE = 300; - - public static Storage getStorage(File rootDir, SignalTypes types, Clock clock) - throws IOException { - return Storage.builder(types) - .setStorageConfiguration(getConfiguration(rootDir)) - .setStorageClock(clock) - .build(); - } + public static final int MAX_FILE_SIZE = 2000; + public static final int MAX_FOLDER_SIZE = 6000; - public static StorageConfiguration getConfiguration(File rootDir) { - return StorageConfiguration.builder() - .setRootDir(rootDir) + public static FileStorageConfiguration getConfiguration() { + return FileStorageConfiguration.builder() .setMaxFileAgeForWriteMillis(MAX_FILE_AGE_FOR_WRITE_MILLIS) .setMinFileAgeForReadMillis(MIN_FILE_AGE_FOR_READ_MILLIS) .setMaxFileAgeForReadMillis(MAX_FILE_AGE_FOR_READ_MILLIS) diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java index 8fdc1d41e..791d80faa 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/ReadableFileTest.java @@ -5,23 +5,20 @@ package io.opentelemetry.contrib.disk.buffering.internal.storage.files; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.FIRST_LOG_RECORD; import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.MAX_FILE_AGE_FOR_READ_MILLIS; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.SECOND_LOG_RECORD; +import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.THIRD_LOG_RECORD; import static io.opentelemetry.contrib.disk.buffering.internal.storage.TestData.getConfiguration; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import io.opentelemetry.api.common.Value; -import io.opentelemetry.api.logs.Severity; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.DeserializationException; import io.opentelemetry.contrib.disk.buffering.internal.serialization.deserializers.SignalDeserializer; -import io.opentelemetry.contrib.disk.buffering.internal.serialization.mapping.logs.models.LogRecordDataImpl; import io.opentelemetry.contrib.disk.buffering.internal.serialization.serializers.SignalSerializer; -import io.opentelemetry.contrib.disk.buffering.internal.storage.files.reader.ProcessResult; -import io.opentelemetry.contrib.disk.buffering.internal.storage.responses.ReadableResult; -import io.opentelemetry.contrib.disk.buffering.testutils.TestData; import io.opentelemetry.sdk.common.Clock; import io.opentelemetry.sdk.logs.data.LogRecordData; import java.io.File; @@ -45,57 +42,13 @@ class ReadableFileTest { private static final long CREATED_TIME_MILLIS = 1000L; private static final SignalSerializer SERIALIZER = SignalSerializer.ofLogs(); private static final SignalDeserializer DESERIALIZER = SignalDeserializer.ofLogs(); - private static final LogRecordData FIRST_LOG_RECORD = - LogRecordDataImpl.builder() - .setResource(TestData.RESOURCE_FULL) - .setSpanContext(TestData.SPAN_CONTEXT) - .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) - .setAttributes(TestData.ATTRIBUTES) - .setBodyValue(Value.of("First log body")) - .setSeverity(Severity.DEBUG) - .setSeverityText("Log severity text") - .setTimestampEpochNanos(100L) - .setObservedTimestampEpochNanos(200L) - .setTotalAttributeCount(3) - .setEventName("") - .build(); - - private static final LogRecordData SECOND_LOG_RECORD = - LogRecordDataImpl.builder() - .setResource(TestData.RESOURCE_FULL) - .setSpanContext(TestData.SPAN_CONTEXT) - .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) - .setAttributes(TestData.ATTRIBUTES) - .setBodyValue(Value.of("Second log body")) - .setSeverity(Severity.DEBUG) - .setSeverityText("Log severity text") - .setTimestampEpochNanos(100L) - .setObservedTimestampEpochNanos(200L) - .setTotalAttributeCount(3) - .setEventName("event") - .build(); - - private static final LogRecordData THIRD_LOG_RECORD = - LogRecordDataImpl.builder() - .setResource(TestData.RESOURCE_FULL) - .setSpanContext(TestData.SPAN_CONTEXT) - .setInstrumentationScopeInfo(TestData.INSTRUMENTATION_SCOPE_INFO_FULL) - .setAttributes(TestData.ATTRIBUTES) - .setBodyValue(Value.of("Third log body")) - .setSeverity(Severity.DEBUG) - .setSeverityText("Log severity text") - .setTimestampEpochNanos(100L) - .setObservedTimestampEpochNanos(200L) - .setTotalAttributeCount(3) - .setEventName("") - .build(); @BeforeEach void setUp() throws IOException { source = new File(dir, "sourceFile"); addFileContents(source); clock = mock(); - readableFile = new ReadableFile(source, CREATED_TIME_MILLIS, clock, getConfiguration(dir)); + readableFile = new ReadableFile(source, CREATED_TIME_MILLIS, clock, getConfiguration()); } @AfterEach @@ -115,58 +68,15 @@ private static void addFileContents(File source) throws IOException { } @Test - void readSingleItemAndRemoveIt() throws IOException { - readableFile.readAndProcess( - bytes -> { - assertThat(deserialize(bytes)).isEqualTo(FIRST_LOG_RECORD); - return ProcessResult.SUCCEEDED; - }); + void readAndRemoveItems() throws IOException { + assertThat(FIRST_LOG_RECORD).isEqualTo(deserialize(readableFile.readNext())); + readableFile.removeTopItem(); List logs = getRemainingDataAndClose(readableFile); - assertThat(logs.size()).isEqualTo(2); - assertThat(logs.get(0)).isEqualTo(SECOND_LOG_RECORD); - assertThat(logs.get(1)).isEqualTo(THIRD_LOG_RECORD); - } - - @Test - void whenProcessingSucceeds_returnSuccessStatus() throws IOException { - assertThat(readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) - .isEqualTo(ReadableResult.SUCCEEDED); - } - - @Test - void whenProcessingFails_returnTryLaterStatus() throws IOException { - assertThat(readableFile.readAndProcess(bytes -> ProcessResult.TRY_LATER)) - .isEqualTo(ReadableResult.TRY_LATER); - } - - @Test - void readMultipleLinesAndRemoveThem() throws IOException { - readableFile.readAndProcess( - bytes -> { - assertDeserializedData(FIRST_LOG_RECORD, bytes); - return ProcessResult.SUCCEEDED; - }); - readableFile.readAndProcess( - bytes -> { - assertDeserializedData(SECOND_LOG_RECORD, bytes); - return ProcessResult.SUCCEEDED; - }); - - List logs = getRemainingDataAndClose(readableFile); - - assertThat(logs.size()).isEqualTo(1); - assertThat(logs.get(0)).isEqualTo(THIRD_LOG_RECORD); - } - - @Test - void whenConsumerReturnsFalse_doNotRemoveLineFromSource() throws IOException { - readableFile.readAndProcess(bytes -> ProcessResult.TRY_LATER); - - List logs = getRemainingDataAndClose(readableFile); - - assertThat(logs.size()).isEqualTo(3); + assertThat(2).isEqualTo(logs.size()); + assertThat(SECOND_LOG_RECORD).isEqualTo(logs.get(0)); + assertThat(THIRD_LOG_RECORD).isEqualTo(logs.get(1)); } @Test @@ -177,15 +87,6 @@ void whenReadingLastLine_deleteOriginalFile_and_close() throws IOException { assertThat(readableFile.isClosed()).isTrue(); } - @Test - void whenTheFileContentIsInvalid_deleteOriginalFile_and_close() throws IOException { - assertThat(readableFile.readAndProcess(bytes -> ProcessResult.CONTENT_INVALID)) - .isEqualTo(ReadableResult.FAILED); - - assertThat(source.exists()).isFalse(); - assertThat(readableFile.isClosed()).isTrue(); - } - @Test void whenNoMoreLinesAvailableToRead_deleteOriginalFile_close_and_returnNoContentStatus() throws IOException { @@ -195,58 +96,38 @@ void whenNoMoreLinesAvailableToRead_deleteOriginalFile_close_and_returnNoContent } ReadableFile emptyReadableFile = - new ReadableFile(emptyFile, CREATED_TIME_MILLIS, clock, getConfiguration(dir)); + new ReadableFile(emptyFile, CREATED_TIME_MILLIS, clock, getConfiguration()); - assertThat(emptyReadableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) - .isEqualTo(ReadableResult.FAILED); + assertThat(emptyReadableFile.readNext()).isNull(); assertThat(emptyReadableFile.isClosed()).isTrue(); assertThat(emptyFile.exists()).isFalse(); } @Test - void - whenReadingAfterTheConfiguredReadingTimeExpired_deleteOriginalFile_close_and_returnFileExpiredException() - throws IOException { - readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED); + void whenReadingAfterTheConfiguredReadingTimeExpired_close() throws IOException { when(clock.now()) .thenReturn(MILLISECONDS.toNanos(CREATED_TIME_MILLIS + MAX_FILE_AGE_FOR_READ_MILLIS)); - assertThat(readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) - .isEqualTo(ReadableResult.FAILED); - + assertThat(readableFile.readNext()).isNull(); assertThat(readableFile.isClosed()).isTrue(); } @Test - void whenReadingAfterClosed_returnFailedStatus() throws IOException { - readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED); + void whenReadingAfterClosed_returnNull() throws IOException { readableFile.close(); - assertThat(readableFile.readAndProcess(bytes -> ProcessResult.SUCCEEDED)) - .isEqualTo(ReadableResult.FAILED); - } - - private static void assertDeserializedData(LogRecordData expected, byte[] bytes) { - try { - List deserialized = DESERIALIZER.deserialize(bytes); - assertThat(deserialized.get(0)).isEqualTo(expected); - } catch (DeserializationException e) { - throw new RuntimeException(e); - } + assertThat(readableFile.readNext()).isNull(); } private static List getRemainingDataAndClose(ReadableFile readableFile) throws IOException { List result = new ArrayList<>(); - ReadableResult readableResult = ReadableResult.SUCCEEDED; - while (readableResult == ReadableResult.SUCCEEDED) { - readableResult = - readableFile.readAndProcess( - bytes -> { - result.add(deserialize(bytes)); - return ProcessResult.SUCCEEDED; - }); + byte[] bytes = readableFile.readNext(); + while (bytes != null) { + result.add(deserialize(bytes)); + readableFile.removeTopItem(); + bytes = readableFile.readNext(); } readableFile.close(); diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java index 91ec94f09..2f3d408d6 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/internal/storage/files/WritableFileTest.java @@ -43,7 +43,7 @@ void setUp() throws IOException { new WritableFile( new File(rootDir, String.valueOf(CREATED_TIME_MILLIS)), CREATED_TIME_MILLIS, - TestData.getConfiguration(rootDir), + TestData.getConfiguration(), clock); } diff --git a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java index 69186f812..d113d9f22 100644 --- a/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java +++ b/disk-buffering/src/test/java/io/opentelemetry/contrib/disk/buffering/testutils/BaseSignalSerializerTest.java @@ -37,7 +37,7 @@ protected byte[] serialize(SIGNAL_SDK_ITEM... items) { protected List deserialize(byte[] source) { try (ByteArrayInputStream in = new ByteArrayInputStream(source)) { StreamReader streamReader = DelimitedProtoStreamReader.Factory.getInstance().create(in); - return getDeserializer().deserialize(Objects.requireNonNull(streamReader.readNext()).content); + return getDeserializer().deserialize(Objects.requireNonNull(streamReader.readNext())); } catch (IOException e) { throw new RuntimeException(e); } From 99eace6f6a0bd2e5bee3f003ee35e854d32d0e3b Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Wed, 8 Oct 2025 02:05:15 +0200 Subject: [PATCH 348/371] fix link check (#2333) --- .mise/tasks/lint/links-in-modified-files.sh | 18 +++++++++++------- .mise/tasks/lint/local-links.sh | 2 +- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/.mise/tasks/lint/links-in-modified-files.sh b/.mise/tasks/lint/links-in-modified-files.sh index 893723a44..74667f292 100755 --- a/.mise/tasks/lint/links-in-modified-files.sh +++ b/.mise/tasks/lint/links-in-modified-files.sh @@ -3,17 +3,19 @@ set -e -#USAGE flag "--base " help="base branch to compare against" default="origin/main" -#USAGE flag "--head " help="head branch to compare against" default="" -#USAGE flag "--event " help="PR name" default="pull_request" +#USAGE flag "--base " help="base branch to compare against (default: origin/main)" default="origin/main" +#USAGE flag "--head " help="head branch to compare against (empty for local changes) (default: empty)" default="" +#USAGE flag "--event " help="event name (default: pull_request)" default="pull_request" -if [ "$usage_head" == "''" ]; then +if [ "$usage_head" = "''" ]; then usage_head="" fi # Check if lychee config was modified -config_modified=$(git diff --name-only --merge-base "$usage_base" "$usage_head" \ - | grep -E '^(\.github/config/lychee\.toml|.mise/tasks/lint|mise\.toml)$' || true) +# shellcheck disable=SC2086 +# - because usage_head may be empty +config_modified=$(git diff --name-only --merge-base "$usage_base" $usage_head \ + | grep -E '^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*|mise\.toml)$' || true) if [ -n "$config_modified" ] ; then echo "config changes, checking all files." @@ -24,7 +26,9 @@ elif [ "$usage_event" != "pull_request" ] ; then else # Using lychee's default extension filter here to match when it runs against all files # Note: --diff-filter=d filters out deleted files - modified_files=$(git diff --name-only --diff-filter=d "$usage_base" "$usage_head" \ + # shellcheck disable=SC2086 + # - because usage_head may be empty + modified_files=$(git diff --name-only --diff-filter=d "$usage_base" $usage_head \ | grep -E '\.(md|mkd|mdx|mdown|mdwn|mkdn|mkdown|markdown|html|htm|txt)$' \ | tr '\n' ' ' || true) diff --git a/.mise/tasks/lint/local-links.sh b/.mise/tasks/lint/local-links.sh index e79694d95..54762bf1e 100755 --- a/.mise/tasks/lint/local-links.sh +++ b/.mise/tasks/lint/local-links.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -#MISE description="Lint links in all files" +#MISE description="Lint links in local files" set -e From 58e96111878e517fb8dbcbd0f6e98f7f4e1615a4 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Wed, 8 Oct 2025 00:28:20 +0000 Subject: [PATCH 349/371] chore(deps): update github/codeql-action action to v3.30.7 (#2335) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3dbad303e..3b845ff19 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 + uses: github/codeql-action/init@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 + uses: github/codeql-action/analyze@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index a4054ef1b..c7fc5ab9d 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@64d10c13136e1c5bce3e5fbde8d4906eeaafc885 # v3.30.6 + uses: github/codeql-action/upload-sarif@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 with: sarif_file: results.sarif From c4e3739c242c963984faa7484fe5838ac39674d7 Mon Sep 17 00:00:00 2001 From: Jay DeLuca Date: Wed, 8 Oct 2025 16:27:46 -0400 Subject: [PATCH 350/371] Fix stale action (#2337) --- .github/workflows/issue-management-stale-action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/issue-management-stale-action.yml b/.github/workflows/issue-management-stale-action.yml index 69682f6a1..1cb3de9a5 100644 --- a/.github/workflows/issue-management-stale-action.yml +++ b/.github/workflows/issue-management-stale-action.yml @@ -45,9 +45,9 @@ jobs: exempt-issue-labels: "needs author feedback" days-before-pr-stale: -1 days-before-pr-close: -1 - days-before-stale: 365 - days-before-close: 0 - close-issue-label: stale + days-before-issue-stale: 365 + days-before-issue-close: 0 + stale-issue-label: stale close-issue-message: > Since there has been no activity on this enhancement for the past year we are closing it to help maintain our backlog. Anyone who would like to work on it is still welcome to do so, and we can re-open it at that time. From df89d8d06b4a47e7dfbfc92d22526d0d98857c18 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 9 Oct 2025 07:48:23 +0300 Subject: [PATCH 351/371] fix(deps): update dependency net.bytebuddy:byte-buddy-agent to v1.17.8 (#2338) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- runtime-attach/runtime-attach-core/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runtime-attach/runtime-attach-core/build.gradle.kts b/runtime-attach/runtime-attach-core/build.gradle.kts index b5ade38c4..0e06aaf52 100644 --- a/runtime-attach/runtime-attach-core/build.gradle.kts +++ b/runtime-attach/runtime-attach-core/build.gradle.kts @@ -7,7 +7,7 @@ description = "To help in create an OpenTelemetry distro able to runtime attach otelJava.moduleName.set("io.opentelemetry.contrib.attach.core") dependencies { - implementation("net.bytebuddy:byte-buddy-agent:1.17.7") + implementation("net.bytebuddy:byte-buddy-agent:1.17.8") // Used by byte-buddy but not brought in as a transitive dependency. compileOnly("com.google.code.findbugs:annotations") From ac7d80df74833481fdd52ed96dae5365d28439df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dominic=20L=C3=BCchinger?= Date: Fri, 10 Oct 2025 01:26:13 +0200 Subject: [PATCH 352/371] fix: Moved from internal Java namespace to correct module name (#2340) Co-authored-by: Trask Stalnaker --- .../internal/RuleBasedRoutingSamplerComponentProviderTest.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) rename samplers/src/test/java/{ => io/opentelemetry/contrib/sampler}/internal/RuleBasedRoutingSamplerComponentProviderTest.java (98%) diff --git a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java b/samplers/src/test/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProviderTest.java similarity index 98% rename from samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java rename to samplers/src/test/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProviderTest.java index 31767262b..cc2ae4129 100644 --- a/samplers/src/test/java/internal/RuleBasedRoutingSamplerComponentProviderTest.java +++ b/samplers/src/test/java/io/opentelemetry/contrib/sampler/internal/RuleBasedRoutingSamplerComponentProviderTest.java @@ -3,7 +3,7 @@ * SPDX-License-Identifier: Apache-2.0 */ -package internal; +package io.opentelemetry.contrib.sampler.internal; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -15,7 +15,6 @@ import io.opentelemetry.api.trace.SpanKind; import io.opentelemetry.context.Context; import io.opentelemetry.contrib.sampler.RuleBasedRoutingSampler; -import io.opentelemetry.contrib.sampler.internal.RuleBasedRoutingSamplerComponentProvider; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.extension.incubator.fileconfig.DeclarativeConfiguration; import io.opentelemetry.sdk.trace.IdGenerator; From bd553854e24fe6aaf69872751b8b55a79680df68 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 9 Oct 2025 21:30:08 -0700 Subject: [PATCH 353/371] Fix daily link checking (#2339) --- .github/workflows/build-daily.yml | 10 +++++++++- .github/workflows/reusable-link-check.yml | 3 +-- .mise/tasks/lint/links-in-modified-files.sh | 4 ---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index 7704cab52..8fe632aec 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -16,7 +16,15 @@ jobs: no-build-cache: true link-check: - uses: ./.github/workflows/reusable-link-check.yml + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 + + - uses: jdx/mise-action@e3d7b8d67a7958d1207f6ed871e83b1ea780e7b0 # v3.3.1 + + - run: mise run lint:links + env: + GITHUB_TOKEN: ${{ github.token }} workflow-notification: permissions: diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index efb8e2874..4c31f58fc 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -17,7 +17,6 @@ jobs: - uses: jdx/mise-action@e3d7b8d67a7958d1207f6ed871e83b1ea780e7b0 # v3.3.1 - name: Link check - relative links (all files) - if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} run: mise run lint:local-links @@ -25,4 +24,4 @@ jobs: - name: Link check (modified files only) env: GITHUB_TOKEN: ${{ github.token }} - run: mise run lint:links-in-modified-files --base origin/${{ github.base_ref }} --head ${{ github.event.pull_request.head.sha }} --event ${{ github.event_name }} + run: mise run lint:links-in-modified-files --base origin/${{ github.base_ref }} --head ${{ github.event.pull_request.head.sha }} diff --git a/.mise/tasks/lint/links-in-modified-files.sh b/.mise/tasks/lint/links-in-modified-files.sh index 74667f292..c3b174ad1 100755 --- a/.mise/tasks/lint/links-in-modified-files.sh +++ b/.mise/tasks/lint/links-in-modified-files.sh @@ -5,7 +5,6 @@ set -e #USAGE flag "--base " help="base branch to compare against (default: origin/main)" default="origin/main" #USAGE flag "--head " help="head branch to compare against (empty for local changes) (default: empty)" default="" -#USAGE flag "--event " help="event name (default: pull_request)" default="pull_request" if [ "$usage_head" = "''" ]; then usage_head="" @@ -20,9 +19,6 @@ config_modified=$(git diff --name-only --merge-base "$usage_base" $usage_head \ if [ -n "$config_modified" ] ; then echo "config changes, checking all files." mise run lint:links -elif [ "$usage_event" != "pull_request" ] ; then - echo "Not a PR - skipping link linting." - exit 0 else # Using lychee's default extension filter here to match when it runs against all files # Note: --diff-filter=d filters out deleted files From 50d579d7275a749ed141bc6188c53d1396556e5c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Thu, 9 Oct 2025 22:19:28 -0700 Subject: [PATCH 354/371] Small gradle simplification (#2341) --- buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index ffd6f2bf0..f512ccf2c 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -148,7 +148,7 @@ dependencies { testing { suites.withType(JvmTestSuite::class).configureEach { dependencies { - implementation(project(project.path)) + implementation(project()) implementation(enforcedPlatform("org.junit:junit-bom:5.14.0")) implementation(enforcedPlatform("org.testcontainers:testcontainers-bom:1.21.3")) From 3a65310b85adfe36b38d4a459e3214de98427900 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 10 Oct 2025 09:11:04 +0200 Subject: [PATCH 355/371] Fix lychee (#2345) --- .github/workflows/build-daily.yml | 10 +--------- .github/workflows/reusable-link-check.yml | 2 +- .mise/tasks/lint/links-in-modified-files.sh | 6 +++++- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/.github/workflows/build-daily.yml b/.github/workflows/build-daily.yml index 8fe632aec..7704cab52 100644 --- a/.github/workflows/build-daily.yml +++ b/.github/workflows/build-daily.yml @@ -16,15 +16,7 @@ jobs: no-build-cache: true link-check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - uses: jdx/mise-action@e3d7b8d67a7958d1207f6ed871e83b1ea780e7b0 # v3.3.1 - - - run: mise run lint:links - env: - GITHUB_TOKEN: ${{ github.token }} + uses: ./.github/workflows/reusable-link-check.yml workflow-notification: permissions: diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 4c31f58fc..161ae6ade 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -24,4 +24,4 @@ jobs: - name: Link check (modified files only) env: GITHUB_TOKEN: ${{ github.token }} - run: mise run lint:links-in-modified-files --base origin/${{ github.base_ref }} --head ${{ github.event.pull_request.head.sha }} + run: mise run lint:links-in-modified-files --base origin/${{ github.base_ref }} --head ${{ github.event.pull_request.head.sha }} --event ${{ github.event_name }} diff --git a/.mise/tasks/lint/links-in-modified-files.sh b/.mise/tasks/lint/links-in-modified-files.sh index c3b174ad1..524cc2d28 100755 --- a/.mise/tasks/lint/links-in-modified-files.sh +++ b/.mise/tasks/lint/links-in-modified-files.sh @@ -5,6 +5,7 @@ set -e #USAGE flag "--base " help="base branch to compare against (default: origin/main)" default="origin/main" #USAGE flag "--head " help="head branch to compare against (empty for local changes) (default: empty)" default="" +#USAGE flag "--event " help="event name (default: pull_request)" default="pull_request" if [ "$usage_head" = "''" ]; then usage_head="" @@ -16,7 +17,10 @@ fi config_modified=$(git diff --name-only --merge-base "$usage_base" $usage_head \ | grep -E '^(\.github/config/lychee\.toml|\.mise/tasks/lint/.*|mise\.toml)$' || true) -if [ -n "$config_modified" ] ; then +if [ "$usage_event" != "pull_request" ] ; then + echo "Not a PR - checking all files." + mise run lint:links +elif [ -n "$config_modified" ] ; then echo "config changes, checking all files." mise run lint:links else From 99abce7dd0a91b360414b82f3f8854befb3514da Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Fri, 10 Oct 2025 11:31:55 +0200 Subject: [PATCH 356/371] fix quoting (#2346) --- .github/workflows/reusable-link-check.yml | 3 ++- .mise/tasks/lint/links.sh | 7 ++++++- .mise/tasks/lint/local-links.sh | 7 ++++++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reusable-link-check.yml b/.github/workflows/reusable-link-check.yml index 161ae6ade..d778dea64 100644 --- a/.github/workflows/reusable-link-check.yml +++ b/.github/workflows/reusable-link-check.yml @@ -12,11 +12,12 @@ jobs: steps: - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 with: - fetch-depth: 0 # needed for merge-base below + fetch-depth: 0 # needed for merge-base used in lint:links-in-modified-files - uses: jdx/mise-action@e3d7b8d67a7958d1207f6ed871e83b1ea780e7b0 # v3.3.1 - name: Link check - relative links (all files) + if: github.event_name == 'pull_request' env: GITHUB_TOKEN: ${{ github.token }} run: mise run lint:local-links diff --git a/.mise/tasks/lint/links.sh b/.mise/tasks/lint/links.sh index 479549f74..f5f708535 100755 --- a/.mise/tasks/lint/links.sh +++ b/.mise/tasks/lint/links.sh @@ -5,4 +5,9 @@ set -e #USAGE arg "" var=#true help="files to check" default="." -lychee --verbose --config .github/config/lychee.toml "$usage_file" +for f in $usage_file; do + echo "Checking links in file: $f" +done + +# shellcheck disable=SC2086 +lychee --verbose --config .github/config/lychee.toml $usage_file diff --git a/.mise/tasks/lint/local-links.sh b/.mise/tasks/lint/local-links.sh index 54762bf1e..f16cd3aa5 100755 --- a/.mise/tasks/lint/local-links.sh +++ b/.mise/tasks/lint/local-links.sh @@ -5,4 +5,9 @@ set -e #USAGE arg "" var=#true help="files to check" default="." -lychee --verbose --scheme file --include-fragments "$usage_file" +for f in $usage_file; do + echo "Checking links in file: $f" +done + +# shellcheck disable=SC2086 +lychee --verbose --scheme file --include-fragments $usage_file From 7a62ab85dc13e16949883facaba75230e1bd8b4e Mon Sep 17 00:00:00 2001 From: Jack Berg <34418638+jack-berg@users.noreply.github.com> Date: Fri, 10 Oct 2025 11:28:29 -0500 Subject: [PATCH 357/371] Update company affiliation for jack-berg (#2348) --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4ba3fdc12..63dd49369 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md). ### Maintainers -- [Jack Berg](https://github.com/jack-berg), New Relic +- [Jack Berg](https://github.com/jack-berg), Grafana Labs - [Jason Plumb](https://github.com/breedx-splk), Splunk - [Lauri Tulmin](https://github.com/laurit), Splunk - [Trask Stalnaker](https://github.com/trask), Microsoft @@ -63,7 +63,7 @@ For more information about the maintainer role, see the [community repository](h ### Approvers -- [Jay DeLuca](https://github.com/jaydeluca), Grafana +- [Jay DeLuca](https://github.com/jaydeluca), Grafana Labs - [John Watson](https://github.com/jkwatson), Cloudera For more information about the approver role, see the [community repository](https://github.com/open-telemetry/community/blob/main/guides/contributor/membership.md#approver). From 6cf90639a1a33eda4043d162b3b5bed3acce3545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9sar?= <56847527+LikeTheSalad@users.noreply.github.com> Date: Sat, 11 Oct 2025 01:57:17 +0200 Subject: [PATCH 358/371] OpAMP callback changes (#2336) --- opamp-client/README.md | 8 +++---- .../opamp/client/OpampClient.java | 15 ++++++++---- .../client/internal/impl/OpampClientImpl.java | 8 +++---- .../internal/impl/OpampClientImplTest.java | 24 ++++++++++--------- 4 files changed, 31 insertions(+), 24 deletions(-) diff --git a/opamp-client/README.md b/opamp-client/README.md index d8c0369a1..a5aa42fb9 100644 --- a/opamp-client/README.md +++ b/opamp-client/README.md @@ -22,16 +22,16 @@ OpampClient client = .build( new OpampClient.Callbacks() { @Override - public void onConnect() {} + public void onConnect(OpampClient client) {} @Override - public void onConnectFailed(@Nullable Throwable throwable) {} + public void onConnectFailed(OpampClient client, @Nullable Throwable throwable) {} @Override - public void onErrorResponse(ServerErrorResponse errorResponse) {} + public void onErrorResponse(OpampClient client, ServerErrorResponse errorResponse) {} @Override - public void onMessage(MessageData messageData) { + public void onMessage(OpampClient client, MessageData messageData) { AgentRemoteConfig remoteConfig = messageData.getRemoteConfig(); if (remoteConfig != null) { // A remote config was received diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java index 19d63eb11..735fa3340 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/OpampClient.java @@ -22,7 +22,7 @@ static OpampClientBuilder builder() { * Sets attributes of the Agent. The attributes will be included in the next outgoing status * report. This is typically used by Agents which allow their AgentDescription to change * dynamically while the OpAMPClient is started. May be also called from {@link - * Callbacks#onMessage(MessageData)}. + * Callbacks#onMessage(OpampClient, MessageData)}. * * @param agentDescription The new agent description. */ @@ -40,16 +40,19 @@ interface Callbacks { * Called when the connection is successfully established to the Server. For WebSocket clients * this is called after the handshake is completed without any error. For HTTP clients this is * called for any request if the response status is OK. + * + * @param client The client that's connected. */ - void onConnect(); + void onConnect(OpampClient client); /** * Called when the connection to the Server cannot be established. May also be called if the * connection is lost and reconnection attempt fails. * + * @param client The client that failed to connect. * @param throwable The exception. */ - void onConnectFailed(@Nullable Throwable throwable); + void onConnectFailed(OpampClient client, @Nullable Throwable throwable); /** * Called when the Server reports an error in response to some previously sent request. Useful @@ -57,9 +60,10 @@ interface Callbacks { * retrying previous operations. The client handles the ErrorResponse_UNAVAILABLE case * internally by performing retries as necessary. * + * @param client The client that received an error response. * @param errorResponse The error returned by the Server. */ - void onErrorResponse(ServerErrorResponse errorResponse); + void onErrorResponse(OpampClient client, ServerErrorResponse errorResponse); /** * Called when the Agent receives a message that needs processing. See {@link MessageData} @@ -70,8 +74,9 @@ interface Callbacks { * onMessage returns. This is advisable if processing can take a long time. In that case * returning quickly is preferable to avoid blocking the {@link OpampClient}. * + * @param client The client that received a message. * @param messageData The server response data that needs processing. */ - void onMessage(MessageData messageData); + void onMessage(OpampClient client, MessageData messageData); } } diff --git a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java index e224135ff..1441093d7 100644 --- a/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java +++ b/opamp-client/src/main/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImpl.java @@ -146,12 +146,12 @@ public void setRemoteConfigStatus(@Nonnull RemoteConfigStatus remoteConfigStatus @Override public void onConnectionSuccess() { - callbacks.onConnect(); + callbacks.onConnect(this); } @Override public void onConnectionFailed(Throwable throwable) { - callbacks.onConnectFailed(throwable); + callbacks.onConnectFailed(this, throwable); } @Override @@ -168,7 +168,7 @@ public void onRequestFailed(Throwable throwable) { preserveFailedRequestRecipe(); if (throwable instanceof OpampServerResponseException) { ServerErrorResponse errorResponse = ((OpampServerResponseException) throwable).errorResponse; - callbacks.onErrorResponse(errorResponse); + callbacks.onErrorResponse(this, errorResponse); } } @@ -195,7 +195,7 @@ private void handleResponsePayload(ServerToAgent response) { } if (notifyOnMessage) { - callbacks.onMessage(messageBuilder.build()); + callbacks.onMessage(this, messageBuilder.build()); } } diff --git a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java index dd5c0b956..020b1b2e2 100644 --- a/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java +++ b/opamp-client/src/test/java/io/opentelemetry/opamp/client/internal/impl/OpampClientImplTest.java @@ -8,6 +8,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -199,7 +200,8 @@ void onSuccess_withChangesToReport_notifyCallbackOnMessage() { // Await for onMessage call await().atMost(Duration.ofSeconds(5)).until(() -> callbacks.onMessageCalls.get() == 1); - verify(callbacks).onMessage(MessageData.builder().setRemoteConfig(remoteConfig).build()); + verify(callbacks) + .onMessage(client, MessageData.builder().setRemoteConfig(remoteConfig).build()); } @Test @@ -214,7 +216,7 @@ void onSuccess_withNoChangesToReport_doNotNotifyCallbackOnMessage() { // Giving some time for the callback to get called await().during(Duration.ofSeconds(1)); - verify(callbacks, never()).onMessage(any()); + verify(callbacks, never()).onMessage(eq(client), any()); } @Test @@ -257,8 +259,8 @@ void onConnectionSuccessful_notifyCallback() { await().atMost(Duration.ofSeconds(5)).until(() -> callbacks.onConnectCalls.get() == 1); - verify(callbacks).onConnect(); - verify(callbacks, never()).onConnectFailed(any()); + verify(callbacks).onConnect(client); + verify(callbacks, never()).onConnectFailed(eq(client), any()); } @Test @@ -301,8 +303,8 @@ void onFailedResponse_withServerErrorData_notifyCallback() { await().atMost(Duration.ofSeconds(5)).until(() -> callbacks.onErrorResponseCalls.get() == 1); - verify(callbacks).onErrorResponse(errorResponse); - verify(callbacks, never()).onMessage(any()); + verify(callbacks).onErrorResponse(client, errorResponse); + verify(callbacks, never()).onMessage(eq(client), any()); } @Test @@ -312,7 +314,7 @@ void onConnectionFailed_notifyCallback() { client.onConnectionFailed(throwable); - verify(callbacks).onConnectFailed(throwable); + verify(callbacks).onConnectFailed(client, throwable); } @Test @@ -450,22 +452,22 @@ private static class TestCallbacks implements OpampClient.Callbacks { private final AtomicInteger onMessageCalls = new AtomicInteger(); @Override - public void onConnect() { + public void onConnect(OpampClient client) { onConnectCalls.incrementAndGet(); } @Override - public void onConnectFailed(@Nullable Throwable throwable) { + public void onConnectFailed(OpampClient client, @Nullable Throwable throwable) { onConnectFailedCalls.incrementAndGet(); } @Override - public void onErrorResponse(ServerErrorResponse errorResponse) { + public void onErrorResponse(OpampClient client, ServerErrorResponse errorResponse) { onErrorResponseCalls.incrementAndGet(); } @Override - public void onMessage(MessageData messageData) { + public void onMessage(OpampClient client, MessageData messageData) { onMessageCalls.incrementAndGet(); } } From 1160a10a8588eca10f5080d7aefcb3f897a8b4bc Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 09:13:18 +0300 Subject: [PATCH 359/371] chore(deps): update github/codeql-action action to v4 (#2353) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .github/workflows/codeql.yml | 4 ++-- .github/workflows/ossf-scorecard.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3b845ff19..3a30d5c89 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -50,7 +50,7 @@ jobs: uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - name: Initialize CodeQL - uses: github/codeql-action/init@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 + uses: github/codeql-action/init@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 with: languages: ${{ matrix.language }} # using "linked" helps to keep up with the latest Kotlin support @@ -65,6 +65,6 @@ jobs: run: ./gradlew assemble --no-build-cache --no-daemon - name: Perform CodeQL analysis - uses: github/codeql-action/analyze@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 + uses: github/codeql-action/analyze@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 with: category: "/language:${{matrix.language}}" diff --git a/.github/workflows/ossf-scorecard.yml b/.github/workflows/ossf-scorecard.yml index c7fc5ab9d..4d180d30d 100644 --- a/.github/workflows/ossf-scorecard.yml +++ b/.github/workflows/ossf-scorecard.yml @@ -52,6 +52,6 @@ jobs: # Upload the results to GitHub's code scanning dashboard (optional). # Commenting out will disable upload of results to your repo's Code Scanning dashboard - name: "Upload to code-scanning" - uses: github/codeql-action/upload-sarif@a8d1ac45b9a34d11fe398d5503176af0d06b303e # v3.30.7 + uses: github/codeql-action/upload-sarif@f443b600d91635bebf5b0d9ebc620189c0d6fba5 # v4.30.8 with: sarif_file: results.sarif From f47fac0d96c87964b7884a7314dadd4c36a89ab7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 09:14:03 +0300 Subject: [PATCH 360/371] fix(deps): update dependency com.squareup.okhttp3:okhttp-bom to v5.2.1 (#2352) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- dependencyManagement/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dependencyManagement/build.gradle.kts b/dependencyManagement/build.gradle.kts index 2221dfd27..9ab06e706 100644 --- a/dependencyManagement/build.gradle.kts +++ b/dependencyManagement/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { api(platform("io.opentelemetry.instrumentation:opentelemetry-instrumentation-bom-alpha:${otelInstrumentationVersion}")) api(platform("com.fasterxml.jackson:jackson-bom:2.20.0")) api(platform("com.google.protobuf:protobuf-bom:4.32.1")) - api(platform("com.squareup.okhttp3:okhttp-bom:5.1.0")) + api(platform("com.squareup.okhttp3:okhttp-bom:5.2.1")) constraints { api("io.opentelemetry.semconv:opentelemetry-semconv:${semconvVersion}") From c89ef28005f7b7b960318915c9b93b85f4213065 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 10:08:01 +0300 Subject: [PATCH 361/371] fix(deps): update dependency org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin to v12.1.7 (#2351) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index e963b8e8e..036cdee27 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:8.0.0") implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.3.0") implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") - implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.6") + implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.7") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2.1") implementation("me.champeau.gradle.japicmp:me.champeau.gradle.japicmp.gradle.plugin:0.4.6") From 7aebc1b1e703a75c0f03830c91978e089e796245 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 13 Oct 2025 09:52:25 -0700 Subject: [PATCH 362/371] Pin otel/opentelemetry-collector-contrib:latest to versioned image (#2347) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: trask <218610+trask@users.noreply.github.com> Co-authored-by: Trask Stalnaker Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/renovate.json5 | 14 ++++++++++++++ .../xray/AwsXrayRemoteSamplerIntegrationTest.java | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/.github/renovate.json5 b/.github/renovate.json5 index a7d6e7672..800574bec 100644 --- a/.github/renovate.json5 +++ b/.github/renovate.json5 @@ -4,6 +4,8 @@ 'config:best-practices', 'helpers:pinGitHubActionDigestsToSemver', ], + ignorePaths: [], // overwrite default ignore which includes **/test/** + // used to update docker image versions used in Java test files ignorePresets: [ ':ignoreModulesAndTests', // needed to keep maven-extension test pom files up-to-date 'workarounds:javaLTSVersions', // Allow all Java major versions, not just LTS @@ -243,5 +245,17 @@ '"https://github.com/(?[^/]+/[^/]+)/zipball/(?.+?)"', ], }, + { + customType: 'regex', + datasourceTemplate: 'docker', + managerFilePatterns: [ + '**/*.java', + ], + matchStrings: [ + '"(?otel/opentelemetry-collector-contrib):(?[^@"]+)(?:@(?sha256:[a-f0-9]+))?"', + ], + autoReplaceStringTemplate: '"{{depName}}:{{newValue}}{{#if newDigest}}@{{newDigest}}{{/if}}"', + versioningTemplate: 'docker', + }, ], } diff --git a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java index fe281e3e2..bf922271c 100644 --- a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java +++ b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java @@ -39,7 +39,7 @@ class AwsXrayRemoteSamplerIntegrationTest { @Container private static final GenericContainer otelCollector = - new GenericContainer<>(DockerImageName.parse("otel/opentelemetry-collector-contrib:latest")) + new GenericContainer<>(DockerImageName.parse("otel/opentelemetry-collector-contrib:0.136.0")) .withExposedPorts(13133, 2000) .waitingFor(Wait.forHttp("/").forPort(13133)) .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("otel-collector"))) From a176209b1f9f8059d6fbe3a90434661267d62b89 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 17:17:08 +0000 Subject: [PATCH 363/371] chore(deps): pin otel/opentelemetry-collector-contrib docker tag to 45392d5 (#2355) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .../contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java index bf922271c..cceeb8be2 100644 --- a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java +++ b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java @@ -39,7 +39,9 @@ class AwsXrayRemoteSamplerIntegrationTest { @Container private static final GenericContainer otelCollector = - new GenericContainer<>(DockerImageName.parse("otel/opentelemetry-collector-contrib:0.136.0")) + new GenericContainer<>( + DockerImageName.parse( + "otel/opentelemetry-collector-contrib:0.136.0@sha256:45392d534c1edcc809c2d112394029246bc679d2ae5ea7081414a1fc74f2c621")) .withExposedPorts(13133, 2000) .waitingFor(Wait.forHttp("/").forPort(13133)) .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("otel-collector"))) From b68a6516f2178ca26e6e7dfdbe1d8a9e92d787d3 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 13 Oct 2025 18:39:46 +0000 Subject: [PATCH 364/371] chore(deps): update otel/opentelemetry-collector-contrib docker tag to v0.137.0 (#2356) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- .../contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java index cceeb8be2..81069c295 100644 --- a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java +++ b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java @@ -41,7 +41,7 @@ class AwsXrayRemoteSamplerIntegrationTest { private static final GenericContainer otelCollector = new GenericContainer<>( DockerImageName.parse( - "otel/opentelemetry-collector-contrib:0.136.0@sha256:45392d534c1edcc809c2d112394029246bc679d2ae5ea7081414a1fc74f2c621")) + "otel/opentelemetry-collector-contrib:0.137.0@sha256:886722fe0f37af9d1fe24d29529253ec59fbf263b3b1df4facaf221373e19d23")) .withExposedPorts(13133, 2000) .waitingFor(Wait.forHttp("/").forPort(13133)) .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("otel-collector"))) From 64e4ad65064ceac4a4d89f40cba18875204497b5 Mon Sep 17 00:00:00 2001 From: jason plumb <75337021+breedx-splk@users.noreply.github.com> Date: Mon, 13 Oct 2025 14:30:27 -0700 Subject: [PATCH 365/371] [consistent-sampling] Fix spec urls (#2358) --- consistent-sampling/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/consistent-sampling/README.md b/consistent-sampling/README.md index 49f4c0565..4f67765ac 100644 --- a/consistent-sampling/README.md +++ b/consistent-sampling/README.md @@ -5,7 +5,7 @@ There are two major components included here. ## Original proposal implementation The original specification for consistent probability sampling is defined by - + and . It supports sampling probabilities that are power of 2 (1, 1/2, 1/4, ...), and uses 8-bit `r-value` and 8-bit `p-value` in tracestate. @@ -14,18 +14,18 @@ The implementation of this proposal is contained by the package `io/opentelemetr * **ConsistentSampler**: abstract base class of all consistent sampler implementations below * **ConsistentAlwaysOffSampler**: - see + see * **ConsistentAlwaysOnSampler**: - see + see * **ConsistentComposedAndSampler**: allows combining two consistent samplers and samples when both samplers would sample * **ConsistentComposedOrSampler**: allows combining two consistent sampler and samples when at least one of both samplers would sample, - see + see * **ConsistentParentBasedSampler**: - see + see * **ConsistentProbabilityBasedSampler**: - see + see * **ConsistentRateLimitingSampler**: a rate limiting sampler based on exponential smoothing that dynamically adjusts the sampling probability based on the estimated rate of spans occurring to satisfy a given rate of sampled spans From 2c7e0c4dba0f2a1067d9c648798651f24c2088b1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:52:11 +0300 Subject: [PATCH 366/371] fix(deps): update develocity packages to v4.2.2 (#2360) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 036cdee27..912402156 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -17,7 +17,7 @@ dependencies { implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.7") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") - implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2.1") + implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2.2") implementation("me.champeau.gradle.japicmp:me.champeau.gradle.japicmp.gradle.plugin:0.4.6") implementation("com.google.auto.value:auto-value-annotations:1.11.0") } diff --git a/settings.gradle.kts b/settings.gradle.kts index 1df4d33ad..ebe476362 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -2,7 +2,7 @@ pluginManagement { plugins { id("com.gradleup.shadow") version "9.2.2" id("io.github.gradle-nexus.publish-plugin") version "2.0.0" - id("com.gradle.develocity") version "4.2.1" + id("com.gradle.develocity") version "4.2.2" } } From deff57eb4b698458604eb45f1a96df71059f8398 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 14 Oct 2025 07:52:36 +0300 Subject: [PATCH 367/371] fix(deps): update all patch versions (#2359) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- buildSrc/build.gradle.kts | 2 +- micrometer-meter-provider/build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 912402156..8eeba390b 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -15,7 +15,7 @@ dependencies { implementation("com.diffplug.spotless:com.diffplug.spotless.gradle.plugin:8.0.0") implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:4.3.0") implementation("net.ltgt.nullaway:net.ltgt.nullaway.gradle.plugin:2.3.0") - implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.7") + implementation("org.owasp.dependencycheck:org.owasp.dependencycheck.gradle.plugin:12.1.8") implementation("ru.vyarus.animalsniffer:ru.vyarus.animalsniffer.gradle.plugin:2.0.1") implementation("com.gradle.develocity:com.gradle.develocity.gradle.plugin:4.2.2") implementation("me.champeau.gradle.japicmp:me.champeau.gradle.japicmp.gradle.plugin:0.4.6") diff --git a/micrometer-meter-provider/build.gradle.kts b/micrometer-meter-provider/build.gradle.kts index 27e61bf1f..2de81eba6 100644 --- a/micrometer-meter-provider/build.gradle.kts +++ b/micrometer-meter-provider/build.gradle.kts @@ -20,14 +20,14 @@ dependencies { annotationProcessor("com.google.auto.value:auto-value") compileOnly("com.google.auto.value:auto-value-annotations") - testImplementation("io.micrometer:micrometer-core:1.15.4") + testImplementation("io.micrometer:micrometer-core:1.15.5") } testing { suites { val integrationTest by registering(JvmTestSuite::class) { dependencies { - implementation("io.micrometer:micrometer-registry-prometheus:1.15.4") + implementation("io.micrometer:micrometer-registry-prometheus:1.15.5") } } } From e8e2b890b3a8322ed5c06779c5d9976d1d17126c Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 14 Oct 2025 08:53:14 -0700 Subject: [PATCH 368/371] Ensure all test suites are run (#2344) Co-authored-by: otelbot <197425009+otelbot@users.noreply.github.com> --- .github/workflows/build-common.yml | 32 +------------------ CONTRIBUTING.md | 6 ---- .../AwsXrayRemoteSamplerIntegrationTest.java | 8 +++-- .../kotlin/otel.java-conventions.gradle.kts | 6 ++++ ibm-mq-metrics/build.gradle.kts | 6 +++- .../target_systems/SolrIntegrationTest.java | 5 +++ .../contrib/jmxscraper/JmxConnectionTest.java | 2 ++ .../target_systems/SolrIntegrationTest.java | 5 +++ .../TargetSystemIntegrationTest.java | 6 ++-- .../runtime-attach/build.gradle.kts | 5 --- 10 files changed, 34 insertions(+), 47 deletions(-) diff --git a/.github/workflows/build-common.yml b/.github/workflows/build-common.yml index 20ea2f153..f467d08d8 100644 --- a/.github/workflows/build-common.yml +++ b/.github/workflows/build-common.yml @@ -111,7 +111,7 @@ jobs: - name: Test run: > - ./gradlew test + ./gradlew check -x spotlessCheck "-PtestJavaVersion=${{ matrix.test-java-version }}" "-Porg.gradle.java.installations.paths=${{ steps.setup-java-test.outputs.path }}" "-Porg.gradle.java.installations.auto-download=false" @@ -122,36 +122,6 @@ jobs: if: ${{ !cancelled() && hashFiles('build-scan.txt') != '' }} run: cat build-scan.txt - integration-test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0 - - - name: Set up JDK for running Gradle - uses: actions/setup-java@dded0888837ed1f317902acf8a20df0ad188d165 # v5.0.0 - with: - distribution: temurin - java-version: 17 - - - name: Set up Gradle - uses: gradle/actions/setup-gradle@4d9f0ba0025fe599b4ebab900eb7f3a1d93ef4c2 # v5.0.0 - with: - cache-read-only: ${{ inputs.cache-read-only }} - - - name: Integration test - run: ./gradlew integrationTest "-PmaxTestRetries=${{ inputs.max-test-retries }}" ${{ inputs.no-build-cache && '--no-build-cache' || '' }} - - - name: Build scan - if: ${{ !cancelled() && hashFiles('build-scan.txt') != '' }} - run: cat build-scan.txt - - - name: Save integration test results - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 - if: always() - with: - name: integration-test-results - path: jmx-metrics/build/reports/tests/integrationTest - markdown-lint-check: uses: ./.github/workflows/reusable-markdown-lint.yml diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 04a50c300..d6e0a0a36 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,12 +31,6 @@ To run the tests: ./gradlew test ``` -Some modules include integration tests that can be run with: - -```bash -./gradlew integrationTest -``` - ## Snapshot Builds Snapshot builds of the `main` branch are available from the Sonatype snapshot repository at: diff --git a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java index 81069c295..873fa29a3 100644 --- a/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java +++ b/aws-xray/src/awsTest/java/io/opentelemetry/contrib/aws/xray/AwsXrayRemoteSamplerIntegrationTest.java @@ -16,13 +16,13 @@ import java.time.Duration; import java.util.Collections; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledIf; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; -import org.testcontainers.junit.jupiter.Testcontainers; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; @@ -31,9 +31,13 @@ // to update sampling rules and assert rough ratios of sampling decisions. In the meantime, it // expects you to update the rules through the dashboard to see the effect on the sampling decisions // that are printed. -@Testcontainers(disabledWithoutDocker = true) +@EnabledIf("hasAwsCredentials") class AwsXrayRemoteSamplerIntegrationTest { + static boolean hasAwsCredentials() { + return System.getenv("AWS_ACCESS_KEY_ID") != null; + } + private static final Logger logger = LoggerFactory.getLogger(AwsXrayRemoteSamplerIntegrationTest.class); diff --git a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts index f512ccf2c..3015a292d 100644 --- a/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts +++ b/buildSrc/src/main/kotlin/otel.java-conventions.gradle.kts @@ -173,6 +173,12 @@ testing { } } +tasks { + check { + dependsOn(testing.suites) + } +} + fun isJavaVersionAllowed(version: JavaVersion): Boolean { if (otelJava.minJavaVersionSupported.get() > version) { return false diff --git a/ibm-mq-metrics/build.gradle.kts b/ibm-mq-metrics/build.gradle.kts index 31e191841..7e489bd53 100644 --- a/ibm-mq-metrics/build.gradle.kts +++ b/ibm-mq-metrics/build.gradle.kts @@ -50,7 +50,11 @@ testing { targets { all { testTask.configure { - shouldRunAfter(tasks.test) + // Jakarta JMS requires Java 11+ + val testJavaVersion: String? by project + if (testJavaVersion == "8") { + enabled = false + } } } } diff --git a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/SolrIntegrationTest.java b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/SolrIntegrationTest.java index 78fc82acd..d851a679d 100644 --- a/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/SolrIntegrationTest.java +++ b/jmx-metrics/src/integrationTest/java/io/opentelemetry/contrib/jmxmetrics/target_systems/SolrIntegrationTest.java @@ -11,11 +11,16 @@ import io.opentelemetry.proto.metrics.v1.Metric; import java.time.Duration; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.junit.jupiter.Container; +// possible workaround on Java 8: +// https://stackoverflow.com/questions/35466461/how-to-connect-with-jmx-from-host-to-docker-container-in-docker-machine +@DisabledOnJre(JRE.JAVA_8) class SolrIntegrationTest extends AbstractIntegrationTest { SolrIntegrationTest() { diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java index 00d7fc124..c55f70669 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/JmxConnectionTest.java @@ -23,12 +23,14 @@ import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.junit.jupiter.Testcontainers; /** * Tests all supported ways to connect to remote JMX interface. This indirectly tests * JmxConnectionBuilder and relies on containers to minimize the JMX/RMI network complications which * are not NAT-friendly. */ +@Testcontainers(disabledWithoutDocker = true) class JmxConnectionTest { // OTLP endpoint is not used in test mode, but still has to be provided diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java index 9cba55701..f077e36f5 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/SolrIntegrationTest.java @@ -14,10 +14,15 @@ import io.opentelemetry.contrib.jmxscraper.assertions.AttributeMatcherGroup; import java.nio.file.Path; import java.time.Duration; +import org.junit.jupiter.api.condition.DisabledOnJre; +import org.junit.jupiter.api.condition.JRE; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.wait.strategy.Wait; +// possible workaround on Java 8: +// https://stackoverflow.com/questions/35466461/how-to-connect-with-jmx-from-host-to-docker-container-in-docker-machine +@DisabledOnJre(JRE.JAVA_8) class SolrIntegrationTest extends TargetSystemIntegrationTest { @Override diff --git a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java index e74e93180..0356affdc 100644 --- a/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java +++ b/jmx-scraper/src/integrationTest/java/io/opentelemetry/contrib/jmxscraper/target_systems/TargetSystemIntegrationTest.java @@ -7,6 +7,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.awaitility.Awaitility.await; +import static org.testcontainers.Testcontainers.exposeHostPorts; import com.linecorp.armeria.server.ServerBuilder; import com.linecorp.armeria.server.grpc.GrpcService; @@ -35,11 +36,12 @@ import org.junit.jupiter.api.io.TempDir; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.Testcontainers; import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.Network; import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.junit.jupiter.Testcontainers; +@Testcontainers(disabledWithoutDocker = true) public abstract class TargetSystemIntegrationTest { private static final Logger logger = LoggerFactory.getLogger(TargetSystemIntegrationTest.class); private static final Logger targetSystemLogger = LoggerFactory.getLogger("TargetSystemContainer"); @@ -72,7 +74,7 @@ static void beforeAll() { network = Network.newNetwork(); otlpServer = new OtlpGrpcServer(); otlpServer.start(); - Testcontainers.exposeHostPorts(otlpServer.httpPort()); + exposeHostPorts(otlpServer.httpPort()); otlpEndpoint = "http://" + OTLP_HOST + ":" + otlpServer.httpPort(); } diff --git a/runtime-attach/runtime-attach/build.gradle.kts b/runtime-attach/runtime-attach/build.gradle.kts index 27927a7f0..b0bc63065 100644 --- a/runtime-attach/runtime-attach/build.gradle.kts +++ b/runtime-attach/runtime-attach/build.gradle.kts @@ -59,9 +59,4 @@ tasks { excludeTestsMatching("AgentDisabledBySystemPropertyTest") } } - - check { - dependsOn(testAgentDisabledByEnvironmentVariable) - dependsOn(testAgentDisabledBySystemProperty) - } } From 7fc1fa8e829c5f9b04437ffb73be3b0e93ce2529 Mon Sep 17 00:00:00 2001 From: jackshirazi Date: Tue, 14 Oct 2025 17:39:04 +0100 Subject: [PATCH 369/371] Return Profiler old interval from setInterval (#2354) --- .../opentelemetry/contrib/inferredspans/InferredSpans.java | 6 ++++-- .../contrib/inferredspans/InferredSpansProcessor.java | 5 +++-- .../inferredspans/internal/InferredSpansConfiguration.java | 4 +++- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java index 76f55db83..0d8e14919 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpans.java @@ -42,10 +42,12 @@ public static boolean isEnabled() { * * @param interval the new profiler interval */ - public static void setProfilerInterval(Duration interval) { + @Nullable + public static Duration setProfilerInterval(Duration interval) { InferredSpansProcessor p = instance; if (p != null) { - p.setProfilerInterval(interval); + return p.setProfilerInterval(interval); } + return null; } } diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java index 19baf3174..c5399002e 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/InferredSpansProcessor.java @@ -57,9 +57,10 @@ public class InferredSpansProcessor implements SpanProcessor { } } - public void setProfilerInterval(Duration interval) { - config.setProfilerInterval(interval); + public Duration setProfilerInterval(Duration interval) { + Duration oldInterval = config.setProfilerInterval(interval); profiler.reschedule(); + return oldInterval; } public static InferredSpansProcessorBuilder builder() { diff --git a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java index 819a5b8cf..4034c560d 100644 --- a/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java +++ b/inferred-spans/src/main/java/io/opentelemetry/contrib/inferredspans/internal/InferredSpansConfiguration.java @@ -84,8 +84,10 @@ public Duration getProfilingInterval() { return profilerInterval; } - public void setProfilerInterval(Duration profilerInterval) { + public Duration setProfilerInterval(Duration profilerInterval) { + Duration oldInterval = this.profilerInterval; this.profilerInterval = profilerInterval; + return oldInterval; } public Duration getProfilingDuration() { From 9226e87472cbb3730b6d0fb29a762d4e17d0d433 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 14 Oct 2025 12:02:05 -0700 Subject: [PATCH 370/371] Convert runtime-attach to use test suites (#2343) --- .../runtime-attach/build.gradle.kts | 69 +++++++++++-------- 1 file changed, 42 insertions(+), 27 deletions(-) diff --git a/runtime-attach/runtime-attach/build.gradle.kts b/runtime-attach/runtime-attach/build.gradle.kts index b0bc63065..15cb3361a 100644 --- a/runtime-attach/runtime-attach/build.gradle.kts +++ b/runtime-attach/runtime-attach/build.gradle.kts @@ -24,6 +24,48 @@ dependencies { testImplementation("org.assertj:assertj-core") } +testing { + suites { + val test by getting(JvmTestSuite::class) { + targets.all { + testTask.configure { + setForkEvery(1) // One JVM by test class to avoid a test class launching a runtime attachment influences the behavior of another test class + filter { + excludeTestsMatching("AgentDisabledByEnvironmentVariableTest") + excludeTestsMatching("AgentDisabledBySystemPropertyTest") + } + } + } + } + + val testAgentDisabledByEnvironmentVariable by registering(JvmTestSuite::class) { + targets.all { + testTask.configure { + setForkEvery(1) + filter { + includeTestsMatching("AgentDisabledByEnvironmentVariableTest") + } + include("**/AgentDisabledByEnvironmentVariableTest.*") + environment("OTEL_JAVAAGENT_ENABLED", "false") + } + } + } + + val testAgentDisabledBySystemProperty by registering(JvmTestSuite::class) { + targets.all { + testTask.configure { + setForkEvery(1) + filter { + includeTestsMatching("AgentDisabledBySystemPropertyTest") + } + include("**/AgentDisabledBySystemPropertyTest.*") + jvmArgs("-Dotel.javaagent.enabled=false") + } + } + } + } +} + tasks { jar { inputs.files(agent) @@ -32,31 +74,4 @@ tasks { }) rename("^(.*)\\.jar\$", "otel-agent.jar") } - - withType().configureEach { - setForkEvery(1) // One JVM by test class to avoid a test class launching a runtime attachment influences the behavior of another test class - } - - val testAgentDisabledByEnvironmentVariable by registering(Test::class) { - filter { - includeTestsMatching("AgentDisabledByEnvironmentVariableTest") - } - include("**/AgentDisabledByEnvironmentVariableTest.*") - environment("OTEL_JAVAAGENT_ENABLED", "false") - } - - val testAgentDisabledBySystemProperty by registering(Test::class) { - filter { - includeTestsMatching("AgentDisabledBySystemPropertyTest") - } - include("**/AgentDisabledBySystemPropertyTest.*") - jvmArgs("-Dotel.javaagent.enabled=false") - } - - test { - filter { - excludeTestsMatching("AgentDisabledByEnvironmentVariableTest") - excludeTestsMatching("AgentDisabledBySystemPropertyTest") - } - } } From 091ebd2f36b5d239a5851e4141717cb03974bd56 Mon Sep 17 00:00:00 2001 From: Trask Stalnaker Date: Tue, 14 Oct 2025 13:15:30 -0700 Subject: [PATCH 371/371] Convert gcp-auth-extension to use test suites (#2342) --- gcp-auth-extension/build.gradle.kts | 107 +++++++++++++++------------- 1 file changed, 56 insertions(+), 51 deletions(-) diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts index d7e99ad30..6cdd858bc 100644 --- a/gcp-auth-extension/build.gradle.kts +++ b/gcp-auth-extension/build.gradle.kts @@ -53,16 +53,64 @@ dependencies { agent("io.opentelemetry.javaagent:opentelemetry-javaagent") } -tasks { - test { - useJUnitPlatform() - // Unset relevant environment variables to provide a clean state for the tests - environment("GOOGLE_CLOUD_PROJECT", "") - environment("GOOGLE_CLOUD_QUOTA_PROJECT", "") - // exclude integration test - exclude("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class") +testing { + suites { + val test by getting(JvmTestSuite::class) { + targets.all { + testTask.configure { + // Unset relevant environment variables to provide a clean state for the tests + environment("GOOGLE_CLOUD_PROJECT", "") + environment("GOOGLE_CLOUD_QUOTA_PROJECT", "") + // exclude integration test + exclude("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class") + } + } + } + + val integrationTestUserCreds by registering(JvmTestSuite::class) { + dependencies { + implementation(project()) + } + + targets.all { + testTask.configure { + dependsOn(tasks.shadowJar) + + // include only the integration test file + include("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class") + + val fakeCredsFilePath = project.file("src/test/resources/fake_user_creds.json").absolutePath + + environment("GOOGLE_CLOUD_QUOTA_PROJECT", "quota-project-id") + environment("GOOGLE_APPLICATION_CREDENTIALS", fakeCredsFilePath) + + val agentJar = configurations.named("agent").map { it.singleFile.absolutePath } + val extensionJarPath = tasks.shadowJar.flatMap { it.archiveFile }.map { it.asFile.absolutePath } + + jvmArgumentProviders.add(CommandLineArgumentProvider { + listOf( + "-javaagent:${agentJar.get()}", + "-Dotel.javaagent.extensions=${extensionJarPath.get()}", + "-Dgoogle.cloud.project=my-gcp-project", + "-Dotel.java.global-autoconfigure.enabled=true", + "-Dotel.exporter.otlp.endpoint=http://localhost:4318", + "-Dotel.resource.providers.gcp.enabled=true", + "-Dotel.traces.exporter=otlp", + "-Dotel.bsp.schedule.delay=2000", + "-Dotel.metrics.exporter=none", + "-Dotel.logs.exporter=none", + "-Dotel.exporter.otlp.protocol=http/protobuf", + "-Dotel.javaagent.debug=false", + "-Dmockserver.logLevel=trace" + ) + }) + } + } + } } +} +tasks { shadowJar { /** * Shaded version of this extension is required when using it as a OpenTelemetry Java Agent @@ -90,46 +138,3 @@ tasks { dependsOn(shadowJar) } } - -val builtLibsDir = layout.buildDirectory.dir("libs").get().asFile.absolutePath -val javaAgentJarPath = "$builtLibsDir/otel-agent.jar" -val authExtensionJarPath = "${tasks.shadowJar.get().archiveFile.get()}" - -tasks.register("copyAgent") { - into(layout.buildDirectory.dir("libs")) - from(configurations.named("agent") { - rename("opentelemetry-javaagent(.*).jar", "otel-agent.jar") - }) -} - -tasks.register("IntegrationTestUserCreds") { - testClassesDirs = sourceSets.test.get().output.classesDirs - classpath = sourceSets.test.get().runtimeClasspath - - dependsOn(tasks.shadowJar) - dependsOn(tasks.named("copyAgent")) - - useJUnitPlatform() - // include only the integration test file - include("io/opentelemetry/contrib/gcp/auth/GcpAuthExtensionEndToEndTest.class") - - val fakeCredsFilePath = project.file("src/test/resources/fake_user_creds.json").absolutePath - - environment("GOOGLE_CLOUD_QUOTA_PROJECT", "quota-project-id") - environment("GOOGLE_APPLICATION_CREDENTIALS", fakeCredsFilePath) - jvmArgs = listOf( - "-javaagent:$javaAgentJarPath", - "-Dotel.javaagent.extensions=$authExtensionJarPath", - "-Dgoogle.cloud.project=my-gcp-project", - "-Dotel.java.global-autoconfigure.enabled=true", - "-Dotel.exporter.otlp.endpoint=http://localhost:4318", - "-Dotel.resource.providers.gcp.enabled=true", - "-Dotel.traces.exporter=otlp", - "-Dotel.bsp.schedule.delay=2000", - "-Dotel.metrics.exporter=none", - "-Dotel.logs.exporter=none", - "-Dotel.exporter.otlp.protocol=http/protobuf", - "-Dotel.javaagent.debug=false", - "-Dmockserver.logLevel=trace" - ) -}