Skip to content

Commit be2656e

Browse files
authored
Added new Lambda Multiple Span Flag (#178)
1 parent 1015be6 commit be2656e

File tree

5 files changed

+156
-18
lines changed

5 files changed

+156
-18
lines changed

src/AWS.Distro.OpenTelemetry.AutoInstrumentation/AwsAttributeKeys.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ internal sealed class AwsAttributeKeys
2828
internal static readonly string AttributeAWSRegion = "aws.region";
2929
internal static readonly string AttributeAWSRequestId = "aws.requestId";
3030
internal static readonly string AttributeAWSTraceFlagSampled = "aws.trace.flag.sampled";
31+
internal static readonly string AttributeAWSTraceLambdaFlagMultipleServer = "aws.trace.lambda.multiple-server";
3132

3233
// The below semantic names were copied over from various sources.
3334
// https://github.com/open-telemetry/opentelemetry-dotnet-contrib/blob/4c6474259ccb08a41eb45ea6424243d4d2c707db/src/OpenTelemetry.Instrumentation.AWS/Implementation/AWSSemanticConventions.cs
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Diagnostics;
5+
using OpenTelemetry;
6+
using static AWS.Distro.OpenTelemetry.AutoInstrumentation.AwsAttributeKeys;
7+
8+
namespace AWS.Distro.OpenTelemetry.AutoInstrumentation;
9+
10+
/// <summary>
11+
/// Simple Span Processor that adds a new Lambda specific flag to the Lambda Handler Span
12+
/// This is used to show that the lambda span belongs to a trace with multiple server spans
13+
/// </summary>
14+
public class AwsLambdaSpanProcessor : BaseProcessor<Activity>
15+
{
16+
private Activity? lambdaActivity;
17+
18+
/// <summary>
19+
/// OnStart caches a reference to the lambda activity if it exists and adds
20+
/// a flag to signify whether there are multiple server spans or not.
21+
/// </summary>
22+
/// <param name="activity"><see cref="Activity"/> to configure</param>
23+
public override void OnStart(Activity activity)
24+
{
25+
if (activity.Source.Name.Equals("OpenTelemetry.Instrumentation.AWSLambda"))
26+
{
27+
this.lambdaActivity = activity;
28+
}
29+
30+
if (activity.Source.Name.Equals("Microsoft.AspNetCore") && activity.ParentId != null &&
31+
(activity.ParentId.Equals(this.lambdaActivity?.SpanId) || activity.ParentId.Equals(this.lambdaActivity?.Id)))
32+
{
33+
this.lambdaActivity.SetTag(AttributeAWSTraceLambdaFlagMultipleServer, "true");
34+
}
35+
}
36+
37+
/// <summary>
38+
/// OnEnd Function
39+
/// </summary>
40+
/// <param name="activity"><see cref="Activity"/> to configure</param>
41+
public override void OnEnd(Activity activity)
42+
{
43+
}
44+
}

src/AWS.Distro.OpenTelemetry.AutoInstrumentation/AwsSpanProcessingUtil.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,17 +115,17 @@ internal static string GetIngressOperation(Activity span)
115115
return InternalOperation;
116116
}
117117

118-
// There is the case where we can run an ASP.NET application in a lambda environment.
119-
// In this case, we need to decide which takes precedence for the aws.local.operation
120-
// between functionName/FunctionHandler or the route template.
121-
else if (IsLambdaEnvironment())
122-
{
123-
return AwsLambdaFunctionName + "/FunctionHandler";
124-
}
118+
// this takes precedence over the FunctionHandler path. Basically, if HttpContextWeakRef exists,
119+
// this means the the span is coming from ASP.NET Core instrumentation and we want the
120+
// operation name to be the API Route
125121
else if (span.GetCustomProperty("HttpContextWeakRef") != null)
126122
{
127123
return GetRouteTemplate(span);
128124
}
125+
else if (IsLambdaEnvironment())
126+
{
127+
return AwsLambdaFunctionName + "/FunctionHandler";
128+
}
129129

130130
return RouteFallback(span);
131131
}

src/AWS.Distro.OpenTelemetry.AutoInstrumentation/Plugin.cs

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,20 +148,24 @@ public void TracerProviderInitialized(TracerProvider tracerProvider)
148148

149149
// We want to be adding the exporter as the last processor in the traceProvider since processors
150150
// are executed in the order they were added to the provider.
151-
if (AwsSpanProcessingUtil.IsLambdaEnvironment() && !this.HasCustomTracesEndpoint())
151+
if (AwsSpanProcessingUtil.IsLambdaEnvironment())
152152
{
153-
Resource processResource = tracerProvider.GetResource();
153+
tracerProvider.AddProcessor(new AwsLambdaSpanProcessor());
154154

155-
// UDP exporter for sampled spans
156-
var sampledSpanExporter = new OtlpUdpExporter(processResource, AwsXrayDaemonAddress, FormatOtelSampledTracesBinaryPrefix);
157-
tracerProvider.AddProcessor(new BatchActivityExportProcessor(exporter: sampledSpanExporter, maxExportBatchSize: LambdaSpanExportBatchSize));
158-
159-
if (this.IsApplicationSignalsEnabled())
155+
if (!this.HasCustomTracesEndpoint())
160156
{
161-
// Register UDP Exporter to export unsampled traces in Lambda
162-
// only when Application Signals enabled
163-
var unsampledSpanExporter = new OtlpUdpExporter(processResource, AwsXrayDaemonAddress, FormatOtelUnSampledTracesBinaryPrefix);
164-
tracerProvider.AddProcessor(new AwsBatchUnsampledSpanExportProcessor(exporter: unsampledSpanExporter, maxExportBatchSize: LambdaSpanExportBatchSize));
157+
Resource processResource = tracerProvider.GetResource();
158+
159+
// UDP exporter for sampled spans
160+
var sampledSpanExporter = new OtlpUdpExporter(processResource, AwsXrayDaemonAddress, FormatOtelSampledTracesBinaryPrefix);
161+
tracerProvider.AddProcessor(new BatchActivityExportProcessor(exporter: sampledSpanExporter, maxExportBatchSize: LambdaSpanExportBatchSize));
162+
if (this.IsApplicationSignalsEnabled())
163+
{
164+
// Register UDP Exporter to export unsampled traces in Lambda
165+
// only when Application Signals enabled
166+
var unsampledSpanExporter = new OtlpUdpExporter(processResource, AwsXrayDaemonAddress, FormatOtelUnSampledTracesBinaryPrefix);
167+
tracerProvider.AddProcessor(new AwsBatchUnsampledSpanExportProcessor(exporter: unsampledSpanExporter, maxExportBatchSize: LambdaSpanExportBatchSize));
168+
}
165169
}
166170
}
167171

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
using System.Diagnostics;
5+
using System.Reflection;
6+
using Moq;
7+
using OpenTelemetry;
8+
using static AWS.Distro.OpenTelemetry.AutoInstrumentation.AwsAttributeKeys;
9+
10+
namespace AWS.Distro.OpenTelemetry.AutoInstrumentation.Tests;
11+
12+
/// <summary>
13+
/// AwsBatchUnsampledSpanExportProcessor test class
14+
/// </summary>
15+
[System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1600:Elements should be documented", Justification = "Tests")]
16+
public class AwsLambdaSpanProcessorTest
17+
{
18+
private ActivitySource lambdaActivitySource = new ActivitySource("OpenTelemetry.Instrumentation.AWSLambda");
19+
private ActivitySource aspNetSource = new ActivitySource("Microsoft.AspNetCore");
20+
private ActivitySource httpSource = new ActivitySource("Microsoft.Http");
21+
22+
public AwsLambdaSpanProcessorTest()
23+
{
24+
var listener = new ActivityListener
25+
{
26+
ShouldListenTo = (activitySource) => true,
27+
Sample = (ref ActivityCreationOptions<ActivityContext> options) => ActivitySamplingResult.AllData,
28+
};
29+
ActivitySource.AddActivityListener(listener);
30+
}
31+
32+
/// <summary>
33+
/// Unit test to check if multiple server spans exists, one from lambda, and
34+
/// other from aspnet core and that AttributeAWSTraceLambdaFlagMultipleServer is set to true.
35+
/// </summary>
36+
[Fact]
37+
public void CheckThatMultipleServerSpanFlagAdded()
38+
{
39+
using var processor = new AwsLambdaSpanProcessor();
40+
41+
var lambdaActivity = this.lambdaActivitySource.StartActivity("lambdaSpan", ActivityKind.Server);
42+
if (lambdaActivity == null)
43+
{
44+
return;
45+
}
46+
47+
Activity.Current = lambdaActivity;
48+
49+
var aspNetCoreActivity = this.aspNetSource.StartActivity("aspSpan", ActivityKind.Server);
50+
if (aspNetCoreActivity == null)
51+
{
52+
return;
53+
}
54+
55+
processor.OnStart(lambdaActivity);
56+
processor.OnStart(aspNetCoreActivity);
57+
processor.Shutdown();
58+
59+
Assert.Equal("true", lambdaActivity.GetTagItem(AttributeAWSTraceLambdaFlagMultipleServer));
60+
}
61+
62+
/// <summary>
63+
/// Unit test to check if only lambda span exists, the flag is not added
64+
/// </summary>
65+
[Fact]
66+
public void CheckThatMultipleServerSpanFlagNotAdded()
67+
{
68+
using var processor = new AwsLambdaSpanProcessor();
69+
70+
var lambdaActivity = this.lambdaActivitySource.StartActivity("lambdaSpan", ActivityKind.Server);
71+
if (lambdaActivity == null)
72+
{
73+
return;
74+
}
75+
76+
Activity.Current = lambdaActivity;
77+
78+
var httpActivity = this.aspNetSource.StartActivity("httpSpan", ActivityKind.Client);
79+
if (httpActivity == null)
80+
{
81+
return;
82+
}
83+
84+
processor.OnStart(lambdaActivity);
85+
processor.Shutdown();
86+
87+
Assert.Null(lambdaActivity.GetTagItem(AttributeAWSTraceLambdaFlagMultipleServer));
88+
}
89+
}

0 commit comments

Comments
 (0)