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 @@ -7,6 +7,10 @@ Notes](../../RELEASENOTES.md).

## Unreleased

* Limit how much of the response body is read when export fails and
error logging is enabled.
([#7017](https://github.com/open-telemetry/opentelemetry-dotnet/pull/7017))

## 1.15.1

Released 2026-Mar-27
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@
#if NETFRAMEWORK
using System.Net.Http;
#endif
#if NET
using System.Buffers;
#endif
using System.Net.Http.Headers;
using System.Text;
using OpenTelemetry.Internal;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
Expand Down Expand Up @@ -65,9 +69,9 @@ public bool Shutdown(int timeoutMilliseconds)
return true;
}

protected static string? TryGetResponseBody(HttpResponseMessage? httpResponse, CancellationToken cancellationToken)
protected internal static string? TryGetResponseBody(HttpResponseMessage? httpResponse, CancellationToken cancellationToken)
{
if (httpResponse?.Content == null)
if (httpResponse?.Content == null || cancellationToken.IsCancellationRequested)
{
return null;
}
Expand All @@ -76,16 +80,95 @@ public bool Shutdown(int timeoutMilliseconds)
{
#if NET
var stream = httpResponse.Content.ReadAsStream(cancellationToken);
using var reader = new StreamReader(stream);
return reader.ReadToEnd();
#else
return httpResponse.Content.ReadAsStringAsync().GetAwaiter().GetResult();
var stream = httpResponse.Content.ReadAsStreamAsync().GetAwaiter().GetResult();
#endif

// See https://github.com/open-telemetry/opentelemetry-proto/pull/781
const int MessageSizeLimit = 4 * 1024 * 1024; // 4MiB

var length = GetBufferLength(stream, MessageSizeLimit);

#if NET
var buffer = ArrayPool<byte>.Shared.Rent(length);
#else
var buffer = new byte[length];
#endif

string result;

try
{
var count = 0;

// Read raw bytes so the size limit applies to bytes rather than characters
while (count < length && !cancellationToken.IsCancellationRequested)
{
var read = stream.Read(buffer, count, length - count);

if (read is 0)
{
break;
}

count += read;
}

// Decode using the charset from the response content headers, if available
var encoding = GetEncoding(httpResponse.Content.Headers.ContentType?.CharSet);
result = encoding.GetString(buffer, 0, count);

if (result.Length is MessageSizeLimit)
{
result += "[TRUNCATED]";
}
}
finally
{
#if NET
ArrayPool<byte>.Shared.Return(buffer);
#endif
}

return result;
}
catch (Exception)
{
return null;
}

static int GetBufferLength(Stream stream, int limit)
{
try
{
// Avoid allocating an overly large buffer if the stream is smaller than the size limit
return stream.Length < limit ? (int)stream.Length : limit;
}
catch (Exception)
{
// Not all Stream types support Length, so default to the maximum
return limit;
}
}

static Encoding GetEncoding(string? name)
{
Encoding encoding = Encoding.UTF8;

if (!string.IsNullOrWhiteSpace(name))
{
try
{
encoding = Encoding.GetEncoding(name);
}
catch (Exception)
{
// Invalid encoding name
}
}

return encoding;
}
}

protected HttpRequestMessage CreateHttpRequest(byte[] buffer, int contentLength)
Expand Down Expand Up @@ -114,16 +197,14 @@ protected HttpRequestMessage CreateHttpRequest(byte[] buffer, int contentLength)
return request;
}

protected HttpResponseMessage SendHttpRequest(HttpRequestMessage request, CancellationToken cancellationToken)
{
protected HttpResponseMessage SendHttpRequest(HttpRequestMessage request, CancellationToken cancellationToken) =>
#if NET
// Note: SendAsync must be used with HTTP/2 because synchronous send is
// not supported.
return this.RequireHttp2 || !SynchronousSendSupportedByCurrentPlatform
this.RequireHttp2 || !SynchronousSendSupportedByCurrentPlatform
? this.HttpClient.SendAsync(request, cancellationToken).GetAwaiter().GetResult()
: this.HttpClient.Send(request, cancellationToken);
#else
return this.HttpClient.SendAsync(request, cancellationToken).GetAwaiter().GetResult();
this.HttpClient.SendAsync(request, cancellationToken).GetAwaiter().GetResult();
#endif
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#if NETFRAMEWORK
using System.Net.Http;
#endif
using System.Diagnostics.Tracing;
using System.Net.Http.Headers;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient.Grpc;

Expand Down Expand Up @@ -123,8 +124,12 @@ public override ExportClientResponse SendExportRequest(byte[] buffer, int conten
catch (HttpRequestException ex)
{
// Handle non-retryable HTTP errors.
var response = TryGetResponseBody(httpResponse, cancellationToken);
OpenTelemetryProtocolExporterEventSource.Log.HttpRequestFailed(this.Endpoint, response, ex);
if (OpenTelemetryProtocolExporterEventSource.Log.IsEnabled(EventLevel.Error, EventKeywords.All))
{
var response = TryGetResponseBody(httpResponse, cancellationToken);
OpenTelemetryProtocolExporterEventSource.Log.HttpRequestFailed(this.Endpoint, response, ex);
}

return new ExportClientGrpcResponse(
success: false,
deadlineUtc: deadlineUtc,
Expand Down Expand Up @@ -165,12 +170,10 @@ public override ExportClientResponse SendExportRequest(byte[] buffer, int conten
}
}

private static bool IsTransientNetworkError(HttpRequestException ex)
{
return ex.InnerException is System.Net.Sockets.SocketException socketEx
&& (socketEx.SocketErrorCode == System.Net.Sockets.SocketError.TimedOut
|| socketEx.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionReset
|| socketEx.SocketErrorCode == System.Net.Sockets.SocketError.HostUnreachable
|| socketEx.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionRefused);
}
private static bool IsTransientNetworkError(HttpRequestException ex) =>
ex.InnerException is System.Net.Sockets.SocketException socketEx
&& (socketEx.SocketErrorCode == System.Net.Sockets.SocketError.TimedOut
|| socketEx.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionReset
|| socketEx.SocketErrorCode == System.Net.Sockets.SocketError.HostUnreachable
|| socketEx.SocketErrorCode == System.Net.Sockets.SocketError.ConnectionRefused);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#if NETFRAMEWORK
using System.Net.Http;
#endif
using System.Diagnostics.Tracing;
using System.Net.Http.Headers;

namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
Expand Down Expand Up @@ -35,8 +36,12 @@ public override ExportClientResponse SendExportRequest(byte[] buffer, int conten
}
catch (HttpRequestException ex)
{
var response = TryGetResponseBody(httpResponse, cancellationToken);
OpenTelemetryProtocolExporterEventSource.Log.HttpRequestFailed(this.Endpoint, response, ex);
if (OpenTelemetryProtocolExporterEventSource.Log.IsEnabled(EventLevel.Error, EventKeywords.All))
{
var response = TryGetResponseBody(httpResponse, cancellationToken);
OpenTelemetryProtocolExporterEventSource.Log.HttpRequestFailed(this.Endpoint, response, ex);
}

return new ExportClientHttpResponse(success: false, deadlineUtc: deadlineUtc, response: httpResponse, ex);
}

Expand Down
Loading
Loading