Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@
*/
public final class CompressorUtil {

private static final Map<String, Compressor> compressorRegistry = buildCompressorRegistry();
private static final Map<String, Compressor> compressorRegistry =
buildCompressorRegistry(CompressorUtil.class.getClassLoader());

private CompressorUtil() {}

Expand All @@ -36,19 +37,36 @@ private CompressorUtil() {}
*/
@Nullable
public static Compressor validateAndResolveCompressor(String compressionMethod) {
Set<String> supportedEncodings = compressorRegistry.keySet();
Compressor compressor = compressorRegistry.get(compressionMethod);
return validateAndResolveCompressor(compressionMethod, null);
}

/**
* Validate that the {@code compressionMethod} is "none" or matches a registered compressor.
*
* @param compressionMethod the compression method to validate and resolve
* @param classLoader the class loader to use for loading SPI implementations, or null to use the
* default
* @return {@code null} if {@code compressionMethod} is "none" or the registered compressor
* @throws IllegalArgumentException if no match is found
*/
@Nullable
public static Compressor validateAndResolveCompressor(
String compressionMethod, @Nullable ClassLoader classLoader) {
Map<String, Compressor> registry =
classLoader == null ? compressorRegistry : buildCompressorRegistry(classLoader);

Set<String> supportedEncodings = registry.keySet();
Compressor compressor = registry.get(compressionMethod);
checkArgument(
"none".equals(compressionMethod) || compressor != null,
"Unsupported compressionMethod. Compression method must be \"none\" or one of: "
+ supportedEncodings.stream().collect(joining(",", "[", "]")));
return compressor;
}

private static Map<String, Compressor> buildCompressorRegistry() {
private static Map<String, Compressor> buildCompressorRegistry(ClassLoader classLoader) {
Map<String, Compressor> compressors = new HashMap<>();
for (CompressorProvider spi :
ServiceLoader.load(CompressorProvider.class, CompressorUtil.class.getClassLoader())) {
for (CompressorProvider spi : ServiceLoader.load(CompressorProvider.class, classLoader)) {
Compressor compressor = spi.getInstance();
compressors.put(compressor.getEncoding(), compressor);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@

package io.opentelemetry.exporter.internal.grpc;

import static java.util.Objects.requireNonNull;

import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.opentelemetry.api.GlobalOpenTelemetry;
Expand All @@ -13,6 +15,7 @@
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
import io.opentelemetry.exporter.internal.compression.Compressor;
import io.opentelemetry.exporter.internal.compression.CompressorUtil;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.sdk.common.InternalTelemetryVersion;
import io.opentelemetry.sdk.common.export.RetryPolicy;
Expand Down Expand Up @@ -115,6 +118,18 @@ public GrpcExporterBuilder<T> setCompression(@Nullable Compressor compressor) {
return this;
}

/**
* Sets the method used to compress payloads. If unset, compression is disabled. Compression
* method "gzip" and "none" are supported out of the box. Support for additional compression
* methods is available by implementing {@link Compressor} and {@link CompressorProvider}.
*/
public GrpcExporterBuilder<T> setCompression(String compressionMethod) {
requireNonNull(compressionMethod, "compressionMethod");
Compressor compressor =
CompressorUtil.validateAndResolveCompressor(compressionMethod, serviceClassLoader);
return setCompression(compressor);
}

public GrpcExporterBuilder<T> setTrustManagerFromCerts(byte[] trustedCertificatesPem) {
tlsConfigHelper.setTrustManagerFromCerts(trustedCertificatesPem);
return this;
Expand Down Expand Up @@ -158,8 +173,8 @@ public GrpcExporterBuilder<T> setInternalTelemetryVersion(
return this;
}

public GrpcExporterBuilder<T> setServiceClassLoader(ClassLoader servieClassLoader) {
this.serviceClassLoader = servieClassLoader;
public GrpcExporterBuilder<T> setServiceClassLoader(ClassLoader serviceClassLoader) {
this.serviceClassLoader = serviceClassLoader;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

package io.opentelemetry.exporter.internal.http;

import static java.util.Objects.requireNonNull;

import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.internal.ConfigUtil;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.ExporterBuilderUtil;
import io.opentelemetry.exporter.internal.TlsConfigHelper;
import io.opentelemetry.exporter.internal.compression.Compressor;
import io.opentelemetry.exporter.internal.compression.CompressorUtil;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.sdk.common.InternalTelemetryVersion;
import io.opentelemetry.sdk.common.export.ProxyOptions;
Expand Down Expand Up @@ -95,6 +98,18 @@ public HttpExporterBuilder<T> setCompression(@Nullable Compressor compressor) {
return this;
}

/**
* Sets the method used to compress payloads. If unset, compression is disabled. Compression
* method "gzip" and "none" are supported out of the box. Support for additional compression
* methods is available by implementing {@link Compressor} and {@link CompressorProvider}.
*/
public HttpExporterBuilder<T> setCompression(String compressionMethod) {
requireNonNull(compressionMethod, "compressionMethod");
Compressor compressor =
CompressorUtil.validateAndResolveCompressor(compressionMethod, serviceClassLoader);
return setCompression(compressor);
}

public HttpExporterBuilder<T> addConstantHeaders(String key, String value) {
constantHeaders.put(key, value);
return this;
Expand Down Expand Up @@ -143,8 +158,8 @@ public HttpExporterBuilder<T> setProxyOptions(ProxyOptions proxyOptions) {
return this;
}

public HttpExporterBuilder<T> setServiceClassLoader(ClassLoader servieClassLoader) {
this.serviceClassLoader = servieClassLoader;
public HttpExporterBuilder<T> setServiceClassLoader(ClassLoader serviceClassLoader) {
this.serviceClassLoader = serviceClassLoader;
return this;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.internal.compression;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.net.URL;
import java.net.URLClassLoader;
import org.junit.jupiter.api.Test;

class CompressorUtilTest {

@Test
void validateAndResolveCompressor_none() {
assertThat(CompressorUtil.validateAndResolveCompressor("none")).isNull();
}

@Test
void validateAndResolveCompressor_gzip() {
assertThat(CompressorUtil.validateAndResolveCompressor("gzip"))
.isEqualTo(GzipCompressor.getInstance());
}

@Test
void validateAndResolveCompressor_invalid() {
assertThatThrownBy(() -> CompressorUtil.validateAndResolveCompressor("invalid"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported compressionMethod");
}

@Test
void validateAndResolveCompressor_withClassLoader_none() {
ClassLoader classLoader = CompressorUtilTest.class.getClassLoader();
assertThat(CompressorUtil.validateAndResolveCompressor("none", classLoader)).isNull();
}

@Test
void validateAndResolveCompressor_withClassLoader_gzip() {
ClassLoader classLoader = CompressorUtilTest.class.getClassLoader();
assertThat(CompressorUtil.validateAndResolveCompressor("gzip", classLoader))
.isEqualTo(GzipCompressor.getInstance());
}

@Test
void validateAndResolveCompressor_withClassLoader_invalid() {
ClassLoader classLoader = CompressorUtilTest.class.getClassLoader();
assertThatThrownBy(() -> CompressorUtil.validateAndResolveCompressor("invalid", classLoader))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported compressionMethod");
}

@Test
void validateAndResolveCompressor_emptyClassLoader() {
// Create a class loader that cannot load CompressorProvider services
ClassLoader emptyClassLoader = new URLClassLoader(new URL[0], null);

// Gzip should still work because it's hardcoded
assertThat(CompressorUtil.validateAndResolveCompressor("gzip", emptyClassLoader))
.isEqualTo(GzipCompressor.getInstance());

// None should still work because it doesn't require loading services
assertThat(CompressorUtil.validateAndResolveCompressor("none", emptyClassLoader)).isNull();

// Any SPI-based compressor should not be available
assertThatThrownBy(
() -> CompressorUtil.validateAndResolveCompressor("base64", emptyClassLoader))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported compressionMethod");
}

@Test
void validateAndResolveCompressor_delegatesCorrectly() {
// Test that single-parameter method delegates to two-parameter method
assertThat(CompressorUtil.validateAndResolveCompressor("gzip"))
.isEqualTo(
CompressorUtil.validateAndResolveCompressor(
"gzip", CompressorUtil.class.getClassLoader()));

assertThat(CompressorUtil.validateAndResolveCompressor("none"))
.isEqualTo(
CompressorUtil.validateAndResolveCompressor(
"none", CompressorUtil.class.getClassLoader()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@
package io.opentelemetry.exporter.internal.grpc;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.opentelemetry.exporter.internal.compression.Compressor;
import io.opentelemetry.exporter.internal.compression.GzipCompressor;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.sdk.internal.StandardComponentId;
import java.net.URI;
import java.net.URL;
import java.net.URLClassLoader;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

Expand All @@ -36,7 +40,7 @@ void compressionDefault() {

@Test
void compressionNone() {
builder.setCompression(null);
builder.setCompression((Compressor) null);

assertThat(builder).extracting("compressor").isNull();
}
Expand All @@ -50,8 +54,44 @@ void compressionGzip() {

@Test
void compressionEnabledAndDisabled() {
builder.setCompression(GzipCompressor.getInstance()).setCompression(null);
builder.setCompression(GzipCompressor.getInstance()).setCompression((Compressor) null);

assertThat(builder).extracting("compressor").isNull();
}

@Test
void compressionString_none() {
builder.setCompression("none");

assertThat(builder).extracting("compressor").isNull();
}

@Test
void compressionString_gzip() {
builder.setCompression("gzip");

assertThat(builder).extracting("compressor").isEqualTo(GzipCompressor.getInstance());
}

@Test
void compressionString_invalid() {
assertThatThrownBy(() -> builder.setCompression("invalid-compression"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported compressionMethod");
}

@Test
void compressionString_usesServiceClassLoader() {
// Create a class loader that cannot load CompressorProvider services
ClassLoader emptyClassLoader = new URLClassLoader(new URL[0], null);
builder.setServiceClassLoader(emptyClassLoader);

// This should still work because gzip compressor is hardcoded
builder.setCompression("gzip");
assertThat(builder).extracting("compressor").isEqualTo(GzipCompressor.getInstance());

// This should still work because "none" doesn't require loading services
builder.setCompression("none");
assertThat(builder).extracting("compressor").isNull();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.exporter.internal.http;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import io.opentelemetry.exporter.internal.compression.Compressor;
import io.opentelemetry.exporter.internal.compression.GzipCompressor;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.sdk.internal.StandardComponentId;
import java.net.URL;
import java.net.URLClassLoader;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

class HttpExporterBuilderTest {

private HttpExporterBuilder<Marshaler> builder;

@BeforeEach
void setUp() {
builder =
new HttpExporterBuilder<>(
StandardComponentId.ExporterType.OTLP_HTTP_SPAN_EXPORTER, "http://localhost:4318");
}

@Test
void compressionDefault() {
assertThat(builder).extracting("compressor").isNull();
}

@Test
void compressionNone() {
builder.setCompression((Compressor) null);

assertThat(builder).extracting("compressor").isNull();
}

@Test
void compressionGzip() {
builder.setCompression(GzipCompressor.getInstance());

assertThat(builder).extracting("compressor").isEqualTo(GzipCompressor.getInstance());
}

@Test
void compressionEnabledAndDisabled() {
builder.setCompression(GzipCompressor.getInstance()).setCompression((Compressor) null);

assertThat(builder).extracting("compressor").isNull();
}

@Test
void compressionString_none() {
builder.setCompression("none");

assertThat(builder).extracting("compressor").isNull();
}

@Test
void compressionString_gzip() {
builder.setCompression("gzip");

assertThat(builder).extracting("compressor").isEqualTo(GzipCompressor.getInstance());
}

@Test
void compressionString_invalid() {
assertThatThrownBy(() -> builder.setCompression("invalid-compression"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessageContaining("Unsupported compressionMethod");
}

@Test
void compressionString_usesServiceClassLoader() {
// Create a class loader that cannot load CompressorProvider services
ClassLoader emptyClassLoader = new URLClassLoader(new URL[0], null);
builder.setServiceClassLoader(emptyClassLoader);

// This should still work because gzip compressor is hardcoded
builder.setCompression("gzip");
assertThat(builder).extracting("compressor").isEqualTo(GzipCompressor.getInstance());

// This should still work because "none" doesn't require loading services
builder.setCompression("none");
assertThat(builder).extracting("compressor").isNull();
}
}
Loading
Loading