Skip to content
Open
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 @@ -18,6 +18,7 @@
import io.opentelemetry.sdk.common.internal.ThrottlingLogger;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import java.util.logging.Level;
Expand All @@ -32,6 +33,8 @@
*/
@SuppressWarnings("checkstyle:JavadocMethod")
public final class HttpExporter {
// Limit logged response body text to avoid flooding warnings with large payloads.
private static final int MAX_RESPONSE_BODY_LOG_LENGTH = 1024;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QQ: Why was this number chosen?


private static final Logger internalLogger = Logger.getLogger(HttpExporter.class.getName());

Expand Down Expand Up @@ -132,10 +135,23 @@ private static String extractErrorStatus(String statusMessage, @Nullable byte[]
if (responseBody == null) {
return "Response body missing, HTTP status message: " + statusMessage;
}
if (responseBody.length == 0) {
return "Response body has 0 length, HTTP status message: " + statusMessage;
}
try {
return GrpcExporterUtil.getStatusMessage(responseBody);
} catch (IOException e) {
return "Unable to parse response body, HTTP status message: " + statusMessage;
return extractResponseBodyMessage(responseBody, statusMessage);
}
}

private static String extractResponseBodyMessage(byte[] responseBody, String statusMessage) {
int lengthToRead = Math.min(responseBody.length, MAX_RESPONSE_BODY_LOG_LENGTH);
String responseBodyText =
new String(responseBody, 0, lengthToRead, StandardCharsets.UTF_8).trim();
if (responseBodyText.isEmpty()) {
return "HTTP status message: " + statusMessage;
}
return "Response body: " + responseBodyText + ", HTTP status message: " + statusMessage;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@
package io.opentelemetry.exporter.otlp.internal;

import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doAnswer;

import io.github.netmikey.logunit.api.LogCapturer;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.metrics.MeterProvider;
import io.opentelemetry.exporter.internal.marshal.Marshaler;
import io.opentelemetry.internal.testing.slf4j.SuppressLogger;
import io.opentelemetry.sdk.common.InternalTelemetryVersion;
Expand All @@ -22,13 +25,33 @@
import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.mockito.Mockito;

class HttpExporterTest {

@RegisterExtension LogCapturer logs = LogCapturer.create().captureForType(HttpExporter.class);

@Test
void build_NoHttpSenderProvider() {
assertThatThrownBy(
() ->
new HttpExporterBuilder(
StandardComponentId.ExporterType.OTLP_HTTP_SPAN_EXPORTER,
"http://localhost")
.build())
.isInstanceOf(IllegalStateException.class)
.hasMessage(
"No HttpSenderProvider found on classpath. Please add dependency on "
+ "opentelemetry-exporter-sender-okhttp or opentelemetry-exporter-sender-jdk");
}

@ParameterizedTest
@EnumSource
@SuppressLogger(HttpExporter.class)
Expand Down Expand Up @@ -197,14 +220,53 @@ void testInternalTelemetry(StandardComponentId.ExporterType exporterType) {
}
}

@Test
@SuppressLogger(HttpExporter.class)
void export_httpJsonErrorBodyUsesBodyTextWithoutGrpcParseWarning() {
HttpSender mockSender = Mockito.mock(HttpSender.class);
Marshaler mockMarshaller = Mockito.mock(Marshaler.class);
HttpExporter exporter =
new HttpExporter(
ComponentId.generateLazy(StandardComponentId.ExporterType.OTLP_HTTP_SPAN_EXPORTER),
mockSender,
MeterProvider::noop,
InternalTelemetryVersion.LATEST,
URI.create("http://testing:1234"),
false);

doAnswer(
invoc -> {
Consumer<HttpResponse> onResponse = invoc.getArgument(1);
onResponse.accept(
new FakeHttpResponse(
500,
"Internal Server Error",
"{\"error\":\"grpc not supported\"}".getBytes(StandardCharsets.UTF_8)));
return null;
})
.when(mockSender)
.send(any(), any(), any());

assertThat(exporter.export(mockMarshaller, 1).join(10, TimeUnit.SECONDS).isSuccess()).isFalse();

logs.assertContains("Response body: {\"error\":\"grpc not supported\"}");
logs.assertDoesNotContain("Unable to parse response body");
}

private static class FakeHttpResponse implements HttpResponse {

final int statusCode;
final String statusMessage;
final byte[] responseBody;

FakeHttpResponse(int statusCode, String statusMessage) {
this(statusCode, statusMessage, new byte[0]);
}

FakeHttpResponse(int statusCode, String statusMessage, byte[] responseBody) {
this.statusCode = statusCode;
this.statusMessage = statusMessage;
this.responseBody = responseBody;
}

@Override
Expand All @@ -219,7 +281,7 @@ public String getStatusMessage() {

@Override
public byte[] getResponseBody() {
return new byte[0];
return responseBody;
}
}
}
Loading