Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Propagate sample rate through tracestate #1147

Merged
merged 7 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion sample/ApiSamples/ApiSamples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0</TargetFrameworks>
<TargetFrameworks>netcoreapp2.1;netcoreapp3.0;net5.0</TargetFrameworks>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
Expand Down
40 changes: 24 additions & 16 deletions sample/ApiSamples/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@
// See the LICENSE file in the project root for more information

using System;
#if NETCOREAPP3_0
using System.Collections.Generic;
#endif
using System.Diagnostics;
using System.Reflection;
using System.Threading;
using Elastic.Apm;
using Elastic.Apm.Api;

namespace ApiSamples
{
/// <summary>
/// This class exercices the Public Agent API.
/// This class exercises the Public Agent API.
/// </summary>
internal class Program
{
private static void Main(string[] args)
{
if (args.Length == 1) //in case it's started with an argument we try to parse the argument as a DistributedTracingData
if (args.Length == 1) //in case it's started with arguments, parse DistributedTracingData from them
{
WriteLineToConsole($"Callee process started - continuing trace with distributed tracing data: {args[0]}");
var transaction2 = Agent.Tracer.StartTransaction("Transaction2", "TestTransaction",
DistributedTracingData.TryDeserializeFromString(args[0]));
var distributedTracingData = DistributedTracingData.TryDeserializeFromString(args[0]);

WriteLineToConsole($"Callee process started - continuing trace with distributed tracing data: {distributedTracingData}");
var transaction2 = Agent.Tracer.StartTransaction("Transaction2", "TestTransaction", distributedTracingData);

try
{
Expand All @@ -35,7 +35,7 @@ private static void Main(string[] args)
transaction2.End();
}

Thread.Sleep(1000);
Thread.Sleep(TimeSpan.FromSeconds(11));
WriteLineToConsole("About to exit");
}
else
Expand All @@ -45,7 +45,7 @@ private static void Main(string[] args)

//WIP: if the process terminates the agent
//potentially does not have time to send the transaction to the server.
Thread.Sleep(1000);
Thread.Sleep(TimeSpan.FromSeconds(11));

WriteLineToConsole("About to exit - press any key...");
Console.ReadKey();
Expand Down Expand Up @@ -160,14 +160,22 @@ public static void PassDistributedTracingData()

//We start the sample app again with a new service name and we pass DistributedTracingData to it
//In the main method we check for this and continue the trace.
var startInfo = new ProcessStartInfo();
startInfo.Environment["ELASTIC_APM_SERVICE_NAME"] = "Service2";
var outgoingDistributedTracingData = (Agent.Tracer.CurrentSpan?.OutgoingDistributedTracingData
?? Agent.Tracer.CurrentTransaction?.OutgoingDistributedTracingData)?.SerializeToString();
startInfo.FileName = "dotnet";
startInfo.Arguments = $"run {outgoingDistributedTracingData}";

var distributedTracingData = Agent.Tracer.CurrentSpan?.OutgoingDistributedTracingData
?? Agent.Tracer.CurrentTransaction?.OutgoingDistributedTracingData;

var traceParent = distributedTracingData?.SerializeToString();
var assembly = Assembly.GetExecutingAssembly().Location;

WriteLineToConsole(
$"Spawning callee process and passing outgoing distributed tracing data: {outgoingDistributedTracingData} to it...");
$"Spawning callee process and passing outgoing distributed tracing data: {traceParent} to it...");
var startInfo = new ProcessStartInfo
{
FileName = "dotnet",
Arguments = $"{assembly} {traceParent}"
};

startInfo.Environment["ELASTIC_APM_SERVICE_NAME"] = "Service2";
var calleeProcess = Process.Start(startInfo);
WriteLineToConsole("Spawned callee process");

Expand Down
25 changes: 14 additions & 11 deletions src/Elastic.Apm.AspNetCore/WebRequestTransactionCreator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Primitives;

namespace Elastic.Apm.AspNetCore
{
Expand All @@ -35,23 +36,25 @@ internal static ITransaction StartTransactionAsync(HttpContext context, IApmLogg
ITransaction transaction;
var transactionName = $"{context.Request.Method} {context.Request.Path}";

if (context.Request.Headers.ContainsKey(TraceContext.TraceParentHeaderNamePrefixed)
|| context.Request.Headers.ContainsKey(TraceContext.TraceParentHeaderName))
{
var headerValue = context.Request.Headers.ContainsKey(TraceContext.TraceParentHeaderName)
? context.Request.Headers[TraceContext.TraceParentHeaderName].ToString()
: context.Request.Headers[TraceContext.TraceParentHeaderNamePrefixed].ToString();
var containsPrefixedTraceParentHeader =
context.Request.Headers.TryGetValue(TraceContext.TraceParentHeaderNamePrefixed, out var traceParentHeader);

var containsTraceParentHeader = false;
if (!containsPrefixedTraceParentHeader)
containsTraceParentHeader = context.Request.Headers.TryGetValue(TraceContext.TraceParentHeaderName, out traceParentHeader);

var tracingData = context.Request.Headers.ContainsKey(TraceContext.TraceStateHeaderName)
? TraceContext.TryExtractTracingData(headerValue, context.Request.Headers[TraceContext.TraceStateHeaderName].ToString())
: TraceContext.TryExtractTracingData(headerValue);
if (containsPrefixedTraceParentHeader || containsTraceParentHeader)
{
var tracingData = context.Request.Headers.TryGetValue(TraceContext.TraceStateHeaderName, out var traceStateHeader)
? TraceContext.TryExtractTracingData(traceParentHeader, traceStateHeader)
: TraceContext.TryExtractTracingData(traceParentHeader);

if (tracingData != null)
{
logger.Debug()
?.Log(
"Incoming request with {TraceParentHeaderName} header. DistributedTracingData: {DistributedTracingData}. Continuing trace.",
TraceContext.TraceParentHeaderNamePrefixed, tracingData);
containsPrefixedTraceParentHeader? TraceContext.TraceParentHeaderNamePrefixed : TraceContext.TraceParentHeaderName, tracingData);

transaction = tracer.StartTransaction(transactionName, ApiConstants.TypeRequest, tracingData);
}
Expand All @@ -60,7 +63,7 @@ internal static ITransaction StartTransactionAsync(HttpContext context, IApmLogg
logger.Debug()
?.Log(
"Incoming request with invalid {TraceParentHeaderName} header (received value: {TraceParentHeaderValue}). Starting trace with new trace id.",
TraceContext.TraceParentHeaderNamePrefixed, headerValue);
containsPrefixedTraceParentHeader? TraceContext.TraceParentHeaderNamePrefixed : TraceContext.TraceParentHeaderName, traceParentHeader);

transaction = tracer.StartTransaction(transactionName, ApiConstants.TypeRequest);
}
Expand Down
17 changes: 7 additions & 10 deletions src/Elastic.Apm.AspNetFullFramework/ElasticApmModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using Elastic.Apm.Helpers;
using Elastic.Apm.Logging;
using Elastic.Apm.Model;
using TraceContext = Elastic.Apm.DistributedTracing.TraceContext;
using Elastic.Apm.Reflection;

namespace Elastic.Apm.AspNetFullFramework
Expand Down Expand Up @@ -138,7 +139,7 @@ private void ProcessBeginRequest(object sender)
_logger.Debug()
?.Log(
"Incoming request with {TraceParentHeaderName} header. DistributedTracingData: {DistributedTracingData} - continuing trace",
DistributedTracing.TraceContext.TraceParentHeaderNamePrefixed, distributedTracingData);
TraceContext.TraceParentHeaderName, distributedTracingData);

// we set ignoreActivity to true to avoid the HttpContext W3C DiagnosticSource issue (see https://github.com/elastic/apm-agent-dotnet/issues/867#issuecomment-650170150)
transaction = Agent.Instance.Tracer.StartTransaction(transactionName, ApiConstants.TypeRequest, distributedTracingData, true);
Expand All @@ -162,27 +163,23 @@ private void ProcessBeginRequest(object sender)
/// <returns>Null if traceparent is not set, otherwise the filled DistributedTracingData instance</returns>
private DistributedTracingData ExtractIncomingDistributedTracingData(HttpRequest request)
{
var traceParentHeaderValue = request.Unvalidated.Headers.Get(DistributedTracing.TraceContext.TraceParentHeaderName);
var traceParentHeaderValue = request.Unvalidated.Headers.Get(TraceContext.TraceParentHeaderName);
// ReSharper disable once InvertIf
if (traceParentHeaderValue == null)
{
traceParentHeaderValue = request.Unvalidated.Headers.Get(DistributedTracing.TraceContext.TraceParentHeaderNamePrefixed);
traceParentHeaderValue = request.Unvalidated.Headers.Get(TraceContext.TraceParentHeaderNamePrefixed);

if (traceParentHeaderValue == null)
{
_logger.Debug()
?.Log("Incoming request doesn't have {TraceParentHeaderName} header - " +
"it means request doesn't have incoming distributed tracing data",
DistributedTracing.TraceContext.TraceParentHeaderNamePrefixed);
"it means request doesn't have incoming distributed tracing data", TraceContext.TraceParentHeaderNamePrefixed);
return null;
}
}

var traceStateHeaderValue = request.Unvalidated.Headers.Get(DistributedTracing.TraceContext.TraceStateHeaderName);

return traceStateHeaderValue != null
? DistributedTracing.TraceContext.TryExtractTracingData(traceParentHeaderValue, traceStateHeaderValue)
: DistributedTracing.TraceContext.TryExtractTracingData(traceParentHeaderValue);
var traceStateHeaderValue = request.Unvalidated.Headers.Get(TraceContext.TraceStateHeaderName);
return TraceContext.TryExtractTracingData(traceParentHeaderValue, traceStateHeaderValue);
}

private static void FillSampledTransactionContextRequest(HttpRequest request, ITransaction transaction)
Expand Down
24 changes: 14 additions & 10 deletions src/Elastic.Apm/Api/DistributedTracingData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@
namespace Elastic.Apm.Api
{
/// <summary>
/// An object encapsulating data passed from the caller to the callee in distributed tracing in order to correlate between
/// them.
/// Its purpose is similar to that of "traceparent" header described at https://www.w3.org/TR/trace-context/
/// See samples/ApiSamples/Program.cs for an example on how to manually pass distributed tracing data between the caller
/// and the callee.
/// Encapsulates distributed tracing data passed from the caller to the callee in order to correlate calls between them.
/// Its purpose is similar to that of traceparent and tracestate headers described in the
/// <a href="https://www.w3.org/TR/trace-context/">Trace Context specification</a>
/// </summary>
/// <example>
/// See sample/ApiSamples/Program.cs for an example on how to manually pass distributed tracing data between the caller
/// and the callee
/// </example>
public class DistributedTracingData
{
internal DistributedTracingData(string traceId, string parentId, bool flagRecorded, string traceState = null)
internal DistributedTracingData(string traceId, string parentId, bool flagRecorded, TraceState traceState = null)
{
TraceId = traceId;
ParentId = parentId;
Expand All @@ -25,11 +27,10 @@ internal DistributedTracingData(string traceId, string parentId, bool flagRecord
}

internal bool FlagRecorded { get; }

internal bool HasTraceState => !string.IsNullOrEmpty(TraceState);
internal bool HasTraceState => TraceState != null;
internal string ParentId { get; }
internal string TraceId { get; }
internal string TraceState { get; }
internal TraceState TraceState { get; }

/// <summary>
/// Serializes this instance to a string.
Expand All @@ -55,7 +56,10 @@ internal DistributedTracingData(string traceId, string parentId, bool flagRecord

public override string ToString() => new ToStringBuilder(nameof(DistributedTracingData))
{
{ "TraceId", TraceId }, { "ParentId", ParentId }, { "FlagRecorded", FlagRecorded }
{ nameof(TraceId), TraceId },
{ nameof(ParentId), ParentId },
{ nameof(FlagRecorded), FlagRecorded },
{ nameof(TraceState), TraceState?.ToTextHeader() }
}.ToString();
}
}
18 changes: 14 additions & 4 deletions src/Elastic.Apm/Config/IConfigurationReader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,11 @@ public interface IConfigurationReader
/// These apply for example to HTTP headers and application/x-www-form-urlencoded data.
/// </summary>
IReadOnlyList<WildcardMatcher> SanitizeFieldNames { get; }

/// <summary>
/// A secret token to ensure that only your agents can send data to your APM server.
/// Both agents and APM server have to be configured with the same secret token.
/// </summary>
string SecretToken { get; }

/// <summary>
Expand All @@ -206,7 +211,7 @@ public interface IConfigurationReader
IReadOnlyList<Uri> ServerUrls { get; }

/// <summary>
/// The URL for APM server
/// The URL for APM server.
/// </summary>
Uri ServerUrl { get; }

Expand Down Expand Up @@ -269,12 +274,17 @@ public interface IConfigurationReader
/// </summary>
int TransactionMaxSpans { get; }

/// <summary>
/// The sample rate for transactions.
/// By default, the agent will sample every transaction (e.g. a request to your service). To reduce overhead and storage requirements,
/// the sample rate can be set to a value between 0.0 and 1.0. The agent still records the overall time and result for unsampled
/// transactions, but no context information, labels, or spans are recorded.
/// </summary>
double TransactionSampleRate { get; }

/// <summary>
/// If true, for all outgoing HTTP requests the agent stores the traceparent in a header prefixed with elastic-apm
/// (elastic-apm-traceparent)
/// otherwise it'll use the official header name from w3c, which is "traceparewnt".
/// If <c>true</c>, for all outgoing HTTP requests the agent stores the traceparent in a "elastic-apm-traceparent" header name.
/// Otherwise, it'll use the official w3c "traceparent" header name.
/// </summary>
bool UseElasticTraceparentHeader { get; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System;
using System.Collections.Generic;
using System.Net.Http;

namespace Elastic.Apm.DiagnosticListeners
Expand All @@ -25,6 +26,15 @@ public HttpDiagnosticListenerCoreImpl(IApmAgent agent)
protected override bool RequestHeadersContain(HttpRequestMessage request, string headerName)
=> request.Headers.Contains(headerName);

protected override bool RequestTryGetHeader(HttpRequestMessage request, string headerName, out string value)
{
var contains = request.Headers.TryGetValues(headerName, out var values);
value = contains
? string.Join(",", values)
: null;
return contains;
}

protected override void RequestHeadersAdd(HttpRequestMessage request, string headerName, string headerValue)
{
if(!string.IsNullOrEmpty(headerValue))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// See the LICENSE file in the project root for more information

using System;
using System.Collections.Generic;
using System.Net;
using System.Reflection;
using Elastic.Apm.Logging;
Expand All @@ -24,11 +25,8 @@ public HttpDiagnosticListenerFullFrameworkImpl(IApmAgent agent)

protected override string RequestGetMethod(HttpWebRequest request) => request.Method;

protected override bool RequestHeadersContain(HttpWebRequest request, string headerName)
{
var values = request.Headers.GetValues(headerName);
return values != null && values.Length > 0;
}
protected override bool RequestHeadersContain(HttpWebRequest request, string headerName) =>
RequestTryGetHeader(request, headerName, out _);

protected override void RequestHeadersAdd(HttpWebRequest request, string headerName, string headerValue)
{
Expand All @@ -38,6 +36,12 @@ protected override void RequestHeadersAdd(HttpWebRequest request, string headerN

protected override int ResponseGetStatusCode(HttpWebResponse response) => (int)response.StatusCode;

protected override bool RequestTryGetHeader(HttpWebRequest request, string headerName, out string value)
{
value = request.Headers.Get(headerName);
return value != null;
}

/// <summary>
/// In Full Framework "System.Net.Http.Desktop.HttpRequestOut.Ex.Stop" does not send the exception property.
/// Therefore we have a specialized ProcessExceptionEvent for Full Framework.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ protected HttpDiagnosticListenerImplBase(IApmAgent agent) : base(agent) { }

protected abstract int ResponseGetStatusCode(TResponse response);

protected abstract bool RequestTryGetHeader(TRequest request, string headerName, out string value);

protected abstract string ExceptionEventKey { get; }

internal abstract string StartEventKey { get; }
Expand Down Expand Up @@ -153,12 +155,15 @@ private void ProcessStartEvent(TRequest request, Uri requestUrl)
}
}

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

//if (!RequestHeadersContain(request, TraceContext.TraceStateHeaderName) && transaction.OutgoingDistributedTracingData != null
// && transaction.OutgoingDistributedTracingData.HasTraceState)
//{
// RequestHeadersAdd(request, TraceContext.TraceStateHeaderName,
// TraceContext.BuildTraceState(transaction.OutgoingDistributedTracingData));
//}

if (span is Span spanInstance)
{
Expand Down
Loading