188 lines
7.9 KiB
C#
188 lines
7.9 KiB
C#
// Copyright The OpenTelemetry Authors
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
using System.Diagnostics;
|
|
#if !NET
|
|
using System.Net.Http;
|
|
#endif
|
|
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation;
|
|
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.ExportClient;
|
|
using OpenTelemetry.Exporter.OpenTelemetryProtocol.Implementation.Serializer;
|
|
using OpenTelemetry.Resources;
|
|
using OpenTelemetry.Trace;
|
|
using Xunit;
|
|
using OtlpCollector = OpenTelemetry.Proto.Collector.Trace.V1;
|
|
|
|
namespace OpenTelemetry.Exporter.OpenTelemetryProtocol.Tests;
|
|
|
|
public class OtlpHttpTraceExportClientTests
|
|
{
|
|
private static readonly SdkLimitOptions DefaultSdkLimitOptions = new();
|
|
|
|
static OtlpHttpTraceExportClientTests()
|
|
{
|
|
Activity.DefaultIdFormat = ActivityIdFormat.W3C;
|
|
Activity.ForceDefaultIdFormat = true;
|
|
|
|
var listener = new ActivityListener
|
|
{
|
|
ShouldListenTo = _ => true,
|
|
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
|
|
};
|
|
|
|
ActivitySource.AddActivityListener(listener);
|
|
}
|
|
|
|
[Fact]
|
|
public void NewOtlpHttpTraceExportClient_OtlpExporterOptions_ExporterHasCorrectProperties()
|
|
{
|
|
var header1 = new { Name = "hdr1", Value = "val1" };
|
|
var header2 = new { Name = "hdr2", Value = "val2" };
|
|
|
|
var options = new OtlpExporterOptions
|
|
{
|
|
Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}",
|
|
};
|
|
|
|
var client = new OtlpHttpExportClient(options, options.HttpClientFactory(), "/v1/traces");
|
|
|
|
Assert.NotNull(client.HttpClient);
|
|
|
|
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]
|
|
[InlineData(true)]
|
|
[InlineData(false)]
|
|
public void SendExportRequest_ExportTraceServiceRequest_SendsCorrectHttpRequest(bool includeServiceNameInResource)
|
|
{
|
|
// Arrange
|
|
var evenTags = new[] { new KeyValuePair<string, object?>("k0", "v0") };
|
|
var oddTags = new[] { new KeyValuePair<string, object?>("k1", "v1") };
|
|
var sources = new[]
|
|
{
|
|
new ActivitySource("even", "2.4.6"),
|
|
new ActivitySource("odd", "1.3.5"),
|
|
};
|
|
var header1 = new { Name = "hdr1", Value = "val1" };
|
|
var header2 = new { Name = "hdr2", Value = "val2" };
|
|
|
|
var options = new OtlpExporterOptions
|
|
{
|
|
Endpoint = new Uri("http://localhost:4317"),
|
|
Headers = $"{header1.Name}={header1.Value}, {header2.Name} = {header2.Value}",
|
|
};
|
|
|
|
var testHttpHandler = new TestHttpMessageHandler();
|
|
|
|
var httpRequestContent = Array.Empty<byte>();
|
|
|
|
var httpClient = new HttpClient(testHttpHandler);
|
|
|
|
var exportClient = new OtlpHttpExportClient(options, httpClient, string.Empty);
|
|
|
|
var resourceBuilder = ResourceBuilder.CreateEmpty();
|
|
if (includeServiceNameInResource)
|
|
{
|
|
resourceBuilder.AddAttributes(
|
|
new List<KeyValuePair<string, object>>
|
|
{
|
|
new(ResourceSemanticConventions.AttributeServiceName, "service_name"),
|
|
new(ResourceSemanticConventions.AttributeServiceNamespace, "ns_1"),
|
|
});
|
|
}
|
|
|
|
var builder = Sdk.CreateTracerProviderBuilder()
|
|
.SetResourceBuilder(resourceBuilder)
|
|
.AddSource(sources[0].Name)
|
|
.AddSource(sources[1].Name);
|
|
|
|
using var openTelemetrySdk = builder.Build();
|
|
|
|
var exportedItems = new List<Activity>();
|
|
var processor = new BatchActivityExportProcessor(new InMemoryExporter<Activity>(exportedItems));
|
|
const int numOfSpans = 10;
|
|
bool isEven;
|
|
for (var i = 0; i < numOfSpans; i++)
|
|
{
|
|
isEven = i % 2 == 0;
|
|
var source = sources[i % 2];
|
|
var activityKind = isEven ? ActivityKind.Client : ActivityKind.Server;
|
|
var activityTags = isEven ? evenTags : oddTags;
|
|
|
|
using Activity? activity = source.StartActivity($"span-{i}", activityKind, parentContext: default, activityTags);
|
|
Assert.NotNull(activity);
|
|
processor.OnEnd(activity);
|
|
}
|
|
|
|
processor.Shutdown();
|
|
|
|
var batch = new Batch<Activity>([.. exportedItems], exportedItems.Count);
|
|
RunTest(batch);
|
|
|
|
void RunTest(Batch<Activity> batch)
|
|
{
|
|
var deadlineUtc = DateTime.UtcNow.AddMilliseconds(httpClient.Timeout.TotalMilliseconds);
|
|
var request = new OtlpCollector.ExportTraceServiceRequest();
|
|
|
|
var (buffer, contentLength) = CreateTraceExportRequest(DefaultSdkLimitOptions, batch, resourceBuilder.Build());
|
|
|
|
// Act
|
|
var result = exportClient.SendExportRequest(buffer, contentLength, deadlineUtc);
|
|
|
|
var httpRequest = testHttpHandler.HttpRequestMessage;
|
|
|
|
// Assert
|
|
Assert.True(result.Success);
|
|
Assert.NotNull(httpRequest);
|
|
Assert.Equal(HttpMethod.Post, httpRequest.Method);
|
|
Assert.NotNull(httpRequest.RequestUri);
|
|
Assert.Equal("http://localhost:4317/", httpRequest.RequestUri.AbsoluteUri);
|
|
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(testHttpHandler.HttpRequestContent);
|
|
|
|
// TODO: Revisit once the HttpClient part is overridden.
|
|
// Assert.IsType<ProtobufOtlpHttpExportClient.ExportRequestContent>(httpRequest.Content);
|
|
Assert.NotNull(httpRequest.Content);
|
|
Assert.Contains(httpRequest.Content.Headers, h => h.Key == "Content-Type" && h.Value.First() == OtlpHttpExportClient.MediaHeaderValue.ToString());
|
|
|
|
var exportTraceRequest = OtlpCollector.ExportTraceServiceRequest.Parser.ParseFrom(testHttpHandler.HttpRequestContent);
|
|
Assert.NotNull(exportTraceRequest);
|
|
Assert.Single(exportTraceRequest.ResourceSpans);
|
|
|
|
var resourceSpan = exportTraceRequest.ResourceSpans.First();
|
|
if (includeServiceNameInResource)
|
|
{
|
|
Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.StringValue == "service_name");
|
|
Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceNamespace && kvp.Value.StringValue == "ns_1");
|
|
}
|
|
else
|
|
{
|
|
Assert.Contains(resourceSpan.Resource.Attributes, (kvp) => kvp.Key == ResourceSemanticConventions.AttributeServiceName && kvp.Value.ToString().Contains("unknown_service:"));
|
|
}
|
|
}
|
|
}
|
|
|
|
private static (byte[] Buffer, int ContentLength) CreateTraceExportRequest(SdkLimitOptions sdkOptions, in Batch<Activity> batch, Resource resource)
|
|
{
|
|
var buffer = new byte[4096];
|
|
var writePosition = ProtobufOtlpTraceSerializer.WriteTraceData(buffer, 0, sdkOptions, resource, batch);
|
|
return (buffer, writePosition);
|
|
}
|
|
}
|