333 lines
12 KiB
C#
333 lines
12 KiB
C#
// <copyright file="JaegerExporterTests.cs" company="OpenTelemetry Authors">
|
|
// Copyright The OpenTelemetry Authors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
// </copyright>
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.Linq;
|
|
using Microsoft.Extensions.DependencyInjection;
|
|
using OpenTelemetry.Exporter.Jaeger.Implementation;
|
|
using OpenTelemetry.Exporter.Jaeger.Implementation.Tests;
|
|
using OpenTelemetry.Resources;
|
|
using OpenTelemetry.Trace;
|
|
using Thrift.Protocol;
|
|
using Xunit;
|
|
|
|
namespace OpenTelemetry.Exporter.Jaeger.Tests
|
|
{
|
|
public class JaegerExporterTests
|
|
{
|
|
[Fact]
|
|
public void JaegerExporter_BadArgs()
|
|
{
|
|
TracerProviderBuilder builder = null;
|
|
Assert.Throws<ArgumentNullException>(() => builder.AddJaegerExporter());
|
|
}
|
|
|
|
[Fact]
|
|
public void JaegerTraceExporter_ctor_NullServiceNameAllowed()
|
|
{
|
|
using var jaegerTraceExporter = new JaegerExporter(new JaegerExporterOptions());
|
|
Assert.NotNull(jaegerTraceExporter);
|
|
}
|
|
|
|
[Fact]
|
|
public void UserHttpFactoryCalled()
|
|
{
|
|
JaegerExporterOptions options = new JaegerExporterOptions();
|
|
|
|
var defaultFactory = options.HttpClientFactory;
|
|
|
|
int invocations = 0;
|
|
options.Protocol = JaegerExportProtocol.HttpBinaryThrift;
|
|
options.HttpClientFactory = () =>
|
|
{
|
|
invocations++;
|
|
return defaultFactory();
|
|
};
|
|
|
|
using (var exporter = new JaegerExporter(options))
|
|
{
|
|
Assert.Equal(1, invocations);
|
|
}
|
|
|
|
using (var provider = Sdk.CreateTracerProviderBuilder()
|
|
.AddJaegerExporter(o =>
|
|
{
|
|
o.Protocol = JaegerExportProtocol.HttpBinaryThrift;
|
|
o.HttpClientFactory = options.HttpClientFactory;
|
|
})
|
|
.Build())
|
|
{
|
|
Assert.Equal(2, invocations);
|
|
}
|
|
|
|
options.HttpClientFactory = null;
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
{
|
|
using var exporter = new JaegerExporter(options);
|
|
});
|
|
|
|
options.HttpClientFactory = () => null;
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
{
|
|
using var exporter = new JaegerExporter(options);
|
|
});
|
|
}
|
|
|
|
[Fact]
|
|
public void ServiceProviderHttpClientFactoryInvoked()
|
|
{
|
|
IServiceCollection services = new ServiceCollection();
|
|
|
|
services.AddHttpClient();
|
|
|
|
int invocations = 0;
|
|
|
|
services.AddHttpClient("JaegerExporter", configureClient: (client) => invocations++);
|
|
|
|
services.AddOpenTelemetryTracing(builder => builder.AddJaegerExporter(
|
|
o => o.Protocol = JaegerExportProtocol.HttpBinaryThrift));
|
|
|
|
using var serviceProvider = services.BuildServiceProvider();
|
|
|
|
var tracerProvider = serviceProvider.GetRequiredService<TracerProvider>();
|
|
|
|
Assert.Equal(1, invocations);
|
|
}
|
|
|
|
[Fact]
|
|
public void JaegerTraceExporter_SetResource_UpdatesServiceName()
|
|
{
|
|
using var jaegerTraceExporter = new JaegerExporter(new JaegerExporterOptions());
|
|
var process = jaegerTraceExporter.Process;
|
|
|
|
process.ServiceName = "TestService";
|
|
|
|
jaegerTraceExporter.SetResourceAndInitializeBatch(Resource.Empty);
|
|
|
|
Assert.Equal("TestService", process.ServiceName);
|
|
|
|
jaegerTraceExporter.SetResourceAndInitializeBatch(ResourceBuilder.CreateEmpty().AddService("MyService").Build());
|
|
|
|
Assert.Equal("MyService", process.ServiceName);
|
|
|
|
jaegerTraceExporter.SetResourceAndInitializeBatch(ResourceBuilder.CreateEmpty().AddService("MyService", "MyNamespace").Build());
|
|
|
|
Assert.Equal("MyNamespace.MyService", process.ServiceName);
|
|
}
|
|
|
|
[Fact]
|
|
public void JaegerTraceExporter_SetResource_CreatesTags()
|
|
{
|
|
using var jaegerTraceExporter = new JaegerExporter(new JaegerExporterOptions());
|
|
var process = jaegerTraceExporter.Process;
|
|
|
|
jaegerTraceExporter.SetResourceAndInitializeBatch(ResourceBuilder.CreateEmpty().AddAttributes(new Dictionary<string, object>
|
|
{
|
|
["Tag"] = "value",
|
|
}).Build());
|
|
|
|
Assert.NotNull(process.Tags);
|
|
Assert.Single(process.Tags);
|
|
Assert.Equal("value", process.Tags["Tag"].VStr);
|
|
}
|
|
|
|
[Fact]
|
|
public void JaegerTraceExporter_SetResource_CombinesTags()
|
|
{
|
|
using var jaegerTraceExporter = new JaegerExporter(new JaegerExporterOptions());
|
|
var process = jaegerTraceExporter.Process;
|
|
|
|
process.Tags = new Dictionary<string, JaegerTag> { ["Tag1"] = new KeyValuePair<string, object>("Tag1", "value1").ToJaegerTag() };
|
|
|
|
jaegerTraceExporter.SetResourceAndInitializeBatch(ResourceBuilder.CreateEmpty().AddAttributes(new Dictionary<string, object>
|
|
{
|
|
["Tag2"] = "value2",
|
|
}).Build());
|
|
|
|
Assert.NotNull(process.Tags);
|
|
Assert.Equal(2, process.Tags.Count);
|
|
Assert.Equal("value1", process.Tags["Tag1"].VStr);
|
|
Assert.Equal("value2", process.Tags["Tag2"].VStr);
|
|
}
|
|
|
|
[Fact]
|
|
public void JaegerTraceExporter_SetResource_IgnoreServiceResources()
|
|
{
|
|
using var jaegerTraceExporter = new JaegerExporter(new JaegerExporterOptions());
|
|
var process = jaegerTraceExporter.Process;
|
|
|
|
jaegerTraceExporter.SetResourceAndInitializeBatch(ResourceBuilder.CreateEmpty().AddAttributes(new Dictionary<string, object>
|
|
{
|
|
[ResourceSemanticConventions.AttributeServiceName] = "servicename",
|
|
[ResourceSemanticConventions.AttributeServiceNamespace] = "servicenamespace",
|
|
}).Build());
|
|
|
|
Assert.Null(process.Tags);
|
|
}
|
|
|
|
[Fact]
|
|
public void JaegerTraceExporter_BuildBatchesToTransmit_FlushedBatch()
|
|
{
|
|
// Arrange
|
|
using var jaegerExporter = new JaegerExporter(new JaegerExporterOptions { MaxPayloadSizeInBytes = 1500 });
|
|
jaegerExporter.SetResourceAndInitializeBatch(Resource.Empty);
|
|
|
|
// Act
|
|
jaegerExporter.AppendSpan(CreateTestJaegerSpan());
|
|
jaegerExporter.AppendSpan(CreateTestJaegerSpan());
|
|
jaegerExporter.AppendSpan(CreateTestJaegerSpan());
|
|
|
|
// Assert
|
|
Assert.Equal(1U, jaegerExporter.NumberOfSpansInCurrentBatch);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("Compact", 1500)]
|
|
[InlineData("Binary", 2200)]
|
|
public void JaegerTraceExporter_SpansSplitToBatches_SpansIncludedInBatches(string protocolType, int maxPayloadSizeInBytes)
|
|
{
|
|
TProtocolFactory protocolFactory = protocolType == "Compact"
|
|
? new TCompactProtocol.Factory()
|
|
: new TBinaryProtocol.Factory();
|
|
var client = new TestJaegerClient();
|
|
|
|
// Arrange
|
|
using var jaegerExporter = new JaegerExporter(
|
|
new JaegerExporterOptions { MaxPayloadSizeInBytes = maxPayloadSizeInBytes },
|
|
protocolFactory,
|
|
client);
|
|
jaegerExporter.SetResourceAndInitializeBatch(Resource.Empty);
|
|
|
|
// Create six spans, each taking more space than the previous one
|
|
var spans = new JaegerSpan[6];
|
|
for (int i = 0; i < 6; i++)
|
|
{
|
|
spans[i] = CreateTestJaegerSpan(
|
|
additionalAttributes: new Dictionary<string, object>
|
|
{
|
|
["foo"] = new string('_', 10 * i),
|
|
});
|
|
}
|
|
|
|
var protocol = protocolFactory.GetProtocol();
|
|
var serializedSpans = spans.Select(s =>
|
|
{
|
|
s.Write(protocol);
|
|
var data = protocol.WrittenData.ToArray();
|
|
protocol.Clear();
|
|
return data;
|
|
}).ToArray();
|
|
|
|
// Act
|
|
var sentBatches = new List<byte[]>();
|
|
foreach (var span in spans)
|
|
{
|
|
jaegerExporter.AppendSpan(span);
|
|
var sentBatch = client.LastWrittenData;
|
|
if (sentBatch != null)
|
|
{
|
|
sentBatches.Add(sentBatch);
|
|
client.LastWrittenData = null;
|
|
}
|
|
}
|
|
|
|
// Assert
|
|
|
|
// Appending the six spans will send two batches with the first four spans
|
|
Assert.Equal(2, sentBatches.Count);
|
|
Assert.True(
|
|
ContainsSequence(sentBatches[0], serializedSpans[0]),
|
|
"Expected span data not found in sent batch");
|
|
Assert.True(
|
|
ContainsSequence(sentBatches[0], serializedSpans[1]),
|
|
"Expected span data not found in sent batch");
|
|
|
|
Assert.True(
|
|
ContainsSequence(sentBatches[1], serializedSpans[2]),
|
|
"Expected span data not found in sent batch");
|
|
Assert.True(
|
|
ContainsSequence(sentBatches[1], serializedSpans[3]),
|
|
"Expected span data not found in sent batch");
|
|
|
|
// jaegerExporter.Batch should contain the two remaining spans
|
|
Assert.Equal(2U, jaegerExporter.NumberOfSpansInCurrentBatch);
|
|
jaegerExporter.SendCurrentBatch();
|
|
Assert.True(client.LastWrittenData != null);
|
|
var serializedBatch = client.LastWrittenData;
|
|
Assert.True(
|
|
ContainsSequence(serializedBatch, serializedSpans[4]),
|
|
"Expected span data not found in unsent batch");
|
|
Assert.True(
|
|
ContainsSequence(serializedBatch, serializedSpans[5]),
|
|
"Expected span data not found in unsent batch");
|
|
}
|
|
|
|
internal static JaegerSpan CreateTestJaegerSpan(
|
|
bool setAttributes = true,
|
|
Dictionary<string, object> additionalAttributes = null,
|
|
bool addEvents = true,
|
|
bool addLinks = true,
|
|
Resource resource = null,
|
|
ActivityKind kind = ActivityKind.Client)
|
|
{
|
|
return JaegerActivityConversionTest
|
|
.CreateTestActivity(
|
|
setAttributes, additionalAttributes, addEvents, addLinks, resource, kind)
|
|
.ToJaegerSpan();
|
|
}
|
|
|
|
private static bool ContainsSequence(byte[] source, byte[] pattern)
|
|
{
|
|
for (var start = 0; start < (source.Length - pattern.Length + 1); start++)
|
|
{
|
|
if (source.Skip(start).Take(pattern.Length).SequenceEqual(pattern))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private sealed class TestJaegerClient : IJaegerClient
|
|
{
|
|
public bool Connected => true;
|
|
|
|
public byte[] LastWrittenData { get; set; }
|
|
|
|
public void Close()
|
|
{
|
|
}
|
|
|
|
public void Connect()
|
|
{
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
}
|
|
|
|
public int Send(byte[] buffer, int offset, int count)
|
|
{
|
|
this.LastWrittenData = new ArraySegment<byte>(buffer, offset, count).ToArray();
|
|
return count;
|
|
}
|
|
}
|
|
}
|
|
}
|