Skip to content

Commit

Permalink
OTLP exporter add User-Agent header (#4120)
Browse files Browse the repository at this point in the history
* OTLP exporter add User-Agent header

* Fix tests

* Update changelog

* Update changelog

* Handle exceptions retrieving assembly version

---------

Co-authored-by: Cijo Thomas <[email protected]>
  • Loading branch information
alanwest and cijothomas authored Feb 1, 2023
1 parent 074c019 commit c0f927f
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 5 deletions.
4 changes: 4 additions & 0 deletions src/OpenTelemetry.Exporter.OpenTelemetryProtocol/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## Unreleased

* Include User-Agent header
[per the specification](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/protocol/exporter.md#user-agent).
([#4120](https://github.com/open-telemetry/opentelemetry-dotnet/pull/4120))

## 1.4.0-rc.2

Released 2023-Jan-09
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ public class OtlpExporterOptions
internal const string TimeoutEnvVarName = "OTEL_EXPORTER_OTLP_TIMEOUT";
internal const string ProtocolEnvVarName = "OTEL_EXPORTER_OTLP_PROTOCOL";

internal static readonly KeyValuePair<string, string>[] StandardHeaders = new KeyValuePair<string, string>[]
{
new KeyValuePair<string, string>("User-Agent", UserAgentProductVersion != null ? $"{UserAgentProduct}/{UserAgentProductVersion}" : UserAgentProduct),
};

internal readonly Func<HttpClient> DefaultHttpClientFactory;

private const string DefaultGrpcEndpoint = "http://localhost:4317";
private const string DefaultHttpEndpoint = "http://localhost:4318";
private const OtlpExportProtocol DefaultOtlpExportProtocol = OtlpExportProtocol.Grpc;
private const string UserAgentProduct = "OTel-OTLP-Exporter-Dotnet";

private static readonly Version UserAgentProductVersion = GetAssemblyVersion();

private Uri endpoint;

Expand Down Expand Up @@ -195,5 +203,18 @@ internal static void RegisterOtlpExporterOptionsFactory(IServiceCollection servi
configuration,
sp.GetRequiredService<IOptionsMonitor<BatchExportActivityProcessorOptions>>().Get(name)));
}

private static Version GetAssemblyVersion()
{
try
{
var assemblyName = typeof(OtlpExporterOptions).Assembly.GetName();
return assemblyName.Version;
}
catch (Exception)
{
return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ public static THeaders GetHeaders<THeaders>(this OtlpExporterOptions options, Ac
});
}

foreach (var header in OtlpExporterOptions.StandardHeaders)
{
addHeader(headers, header.Key, header.Value);
}

return headers;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,14 @@ public void NewOtlpHttpTraceExportClient_OtlpExporterOptions_ExporterHasCorrectP

Assert.NotNull(client.HttpClient);

Assert.Equal(2, client.Headers.Count);
Assert.Equal(2 + OtlpExporterOptions.StandardHeaders.Length, client.Headers.Count);
Assert.Contains(client.Headers, kvp => kvp.Key == header1.Name && kvp.Value == header1.Value);
Assert.Contains(client.Headers, kvp => kvp.Key == header2.Name && kvp.Value == header2.Value);

for (int i = 0; i < OtlpExporterOptions.StandardHeaders.Length; i++)
{
Assert.Contains(client.Headers, entry => entry.Key == OtlpExporterOptions.StandardHeaders[i].Key && entry.Value == OtlpExporterOptions.StandardHeaders[i].Value);
}
}

[Theory]
Expand Down Expand Up @@ -179,10 +184,15 @@ void RunTest(Batch<Activity> batch)
Assert.NotNull(httpRequest);
Assert.Equal(HttpMethod.Post, httpRequest.Method);
Assert.Equal("http://localhost:4317/", httpRequest.RequestUri.AbsoluteUri);
Assert.Equal(2, httpRequest.Headers.Count());
Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + 2, httpRequest.Headers.Count());
Assert.Contains(httpRequest.Headers, h => h.Key == header1.Name && h.Value.First() == header1.Value);
Assert.Contains(httpRequest.Headers, h => h.Key == header2.Name && h.Value.First() == header2.Value);

for (int i = 0; i < OtlpExporterOptions.StandardHeaders.Length; i++)
{
Assert.Contains(httpRequest.Headers, entry => entry.Key == OtlpExporterOptions.StandardHeaders[i].Key && entry.Value.First() == OtlpExporterOptions.StandardHeaders[i].Value);
}

Assert.NotNull(httpRequest.Content);
Assert.IsType<OtlpHttpTraceExportClient.ExportRequestContent>(httpRequest.Content);
Assert.Contains(httpRequest.Content.Headers, h => h.Key == "Content-Type" && h.Value.First() == OtlpHttpTraceExportClient.MediaContentType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
// limitations under the License.
// </copyright>

using Grpc.Core;
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
using Xunit;
using Xunit.Sdk;
Expand All @@ -37,12 +38,19 @@ public void GetMetadataFromHeadersWorksCorrectFormat(string headers, string[] ke
};
var metadata = options.GetMetadataFromHeaders();

Assert.Equal(keys.Length, metadata.Count);
Assert.Equal(OtlpExporterOptions.StandardHeaders.Length + keys.Length, metadata.Count);

for (int i = 0; i < keys.Length; i++)
{
Assert.Contains(metadata, entry => entry.Key == keys[i] && entry.Value == values[i]);
}

for (int i = 0; i < OtlpExporterOptions.StandardHeaders.Length; i++)
{
// Metadata key is always converted to lowercase.
// See: https://cloud.google.com/dotnet/docs/reference/Grpc.Core/latest/Grpc.Core.Metadata.Entry#Grpc_Core_Metadata_Entry__ctor_System_String_System_String_
Assert.Contains(metadata, entry => entry.Key == OtlpExporterOptions.StandardHeaders[i].Key.ToLower() && entry.Value == OtlpExporterOptions.StandardHeaders[i].Value);
}
}

[Theory]
Expand Down Expand Up @@ -71,7 +79,7 @@ public void GetMetadataFromHeadersThrowsExceptionOnInvalidFormat(string headers)
[Theory]
[InlineData("")]
[InlineData(null)]
public void GetHeaders_NoOptionHeaders_ReturnsEmptyHeaders(string optionHeaders)
public void GetHeaders_NoOptionHeaders_ReturnsStandardHeaders(string optionHeaders)
{
var options = new OtlpExporterOptions
{
Expand All @@ -80,7 +88,12 @@ public void GetHeaders_NoOptionHeaders_ReturnsEmptyHeaders(string optionHeaders)

var headers = options.GetHeaders<Dictionary<string, string>>((d, k, v) => d.Add(k, v));

Assert.Empty(headers);
Assert.Equal(OtlpExporterOptions.StandardHeaders.Length, headers.Count);

for (int i = 0; i < OtlpExporterOptions.StandardHeaders.Length; i++)
{
Assert.Contains(headers, entry => entry.Key == OtlpExporterOptions.StandardHeaders[i].Key && entry.Value == OtlpExporterOptions.StandardHeaders[i].Value);
}
}

[Theory]
Expand Down

0 comments on commit c0f927f

Please sign in to comment.