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 @@ -124,35 +124,40 @@ protected override void HandleOnNext(KeyValuePair<string, object> kv)

private void ProcessStartEvent(TRequest request, Uri requestUrl)
{
if (_realAgent?.TracerInternal.CurrentSpan is Span currentSpan)
{
// if the current span is an exit span, don't create a span for the current request
if (currentSpan.InstrumentationFlag == InstrumentationFlag.Azure || currentSpan.InstrumentationFlag == InstrumentationFlag.Elasticsearch)
return;
}

Logger.Trace()?.Log("Processing start event... Request URL: {RequestUrl}", Http.Sanitize(requestUrl));

var transaction = ApmAgent.Tracer.CurrentTransaction;
if (transaction == null)
if (transaction is null)
{
Logger.Debug()?.Log("No current transaction, skip creating span for outgoing HTTP request");
return;
}

var method = RequestGetMethod(request);
string HeaderGetter(string header) => RequestTryGetHeader(request, header, out var value) ? value : null;
if (_realAgent?.TracerInternal.CurrentSpan is Span currentSpan)
{
// if the current span is an exit span, don't create a span for the current request
// but still propagate trace context
if (currentSpan.InstrumentationFlag == InstrumentationFlag.Azure
|| currentSpan.InstrumentationFlag == InstrumentationFlag.Elasticsearch)
{
PropagateTraceContext(request, transaction, currentSpan);
return;
}
}

var method = RequestGetMethod(request);
ISpan span = null;
if (_configuration?.HasTracers ?? false)
{
using (var httpTracers = _configuration.GetTracers())
{
foreach (var httpSpanTracer in httpTracers)
{
if (httpSpanTracer.IsMatch(method, requestUrl, HeaderGetter))
if (httpSpanTracer.IsMatch(method, requestUrl,
header => RequestTryGetHeader(request, header, out var value) ? value : null))
{
span = httpSpanTracer.StartSpan(ApmAgent, method, requestUrl, HeaderGetter);
span = httpSpanTracer.StartSpan(ApmAgent, method, requestUrl,
header => RequestTryGetHeader(request, header, out var value) ? value : null);
if (span != null)
break;
}
Expand Down Expand Up @@ -187,6 +192,20 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl)
return;
}

PropagateTraceContext(request, transaction, span);

if (span is Span realSpan)
{
if (!realSpan.ShouldBeSentToApmServer)
return;
}

span.Context.Http = new Http { Method = method };
span.Context.Http.SetUrl(requestUrl);
}

private void PropagateTraceContext(TRequest request, ITransaction transaction, ISpan span)
{
if (!RequestHeadersContain(request, TraceContext.TraceParentHeaderName))
// We call TraceParent.BuildTraceparent explicitly instead of DistributedTracingData.SerializeToString because
// in the future we might change DistributedTracingData.SerializeToString to use some other internal format
Expand All @@ -205,17 +224,9 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl)
}
}

if (!RequestHeadersContain(request, TraceContext.TraceStateHeaderName) && span.OutgoingDistributedTracingData != null && span.OutgoingDistributedTracingData.HasTraceState)
if (!RequestHeadersContain(request, TraceContext.TraceStateHeaderName) && span.OutgoingDistributedTracingData != null
&& span.OutgoingDistributedTracingData.HasTraceState)
RequestHeadersAdd(request, TraceContext.TraceStateHeaderName, span.OutgoingDistributedTracingData.TraceState.ToTextHeader());

if (span is Span realSpan)
{
if (!realSpan.ShouldBeSentToApmServer)
return;
}

span.Context.Http = new Http { Method = method };
span.Context.Http.SetUrl(requestUrl);
}

private void ProcessStopEvent(object eventValue, TRequest request, Uri requestUrl)
Expand Down
45 changes: 45 additions & 0 deletions test/Elastic.Apm.Elasticsearch.Tests/TraceContextTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Licensed to Elasticsearch B.V under
// one or more agreements.
// Elasticsearch B.V licenses this file to you under the Apache 2.0 License.
// See the LICENSE file in the project root for more information

using System;
using Elastic.Apm.Api;
using Elastic.Apm.DiagnosticSource;
using Elastic.Apm.Tests.Utilities;
using Elasticsearch.Net;
using FluentAssertions;
using Xunit;

namespace Elastic.Apm.Elasticsearch.Tests
{
public class TraceContextTests
{
[Fact]
public void Call_to_Elasticsearch_propagates_Trace_Context_when_HttpDiagnosticsSubscriber_subscribed()
{
using var localServer = LocalServer.Create(context =>
{
var traceparent = context.Request.Headers.Get("traceparent");
traceparent.Should().NotBeNullOrEmpty();

var elasticTraceparent = context.Request.Headers.Get("elastic-apm-traceparent");
elasticTraceparent.Should().NotBeNullOrEmpty().And.Be(traceparent);

var tracestate = context.Request.Headers.Get("tracestate");
tracestate.Should().NotBeNullOrEmpty().And.Contain("es=s:1");

context.Response.StatusCode = 200;
});

using var agent = new ApmAgent(new TestAgentComponents(payloadSender: new MockPayloadSender()));
using var subscribe = agent.Subscribe(new ElasticsearchDiagnosticsSubscriber(), new HttpDiagnosticsSubscriber());

var client = new ElasticLowLevelClient(new ConnectionConfiguration(new Uri(localServer.Uri)));
agent.Tracer.CaptureTransaction("Transaction", ApiConstants.TypeDb, t =>
{
var response = client.Cat.Indices<StringResponse>();
});
}
}
}