Skip to content

Commit 4ec65b4

Browse files
committed
Remove dependency on IMethodAspectHandler. Add WithTracing to source generator. add dependency Amazon.Lambda.Serialization.SystemTextJson
1 parent 5eb8338 commit 4ec65b4

14 files changed

+1000
-313
lines changed

libraries/src/AWS.Lambda.Powertools.Tracing/AWS.Lambda.Powertools.Tracing.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
<PackageReference Include="AWSSDK.XRay" />
1717
<PackageReference Include="AWSXRayRecorder.Core" />
1818
<PackageReference Include="AWSXRayRecorder.Handlers.AwsSdk" />
19+
<PackageReference Include="Amazon.Lambda.Serialization.SystemTextJson" />
1920
<ProjectReference Include="..\AWS.Lambda.Powertools.Common\AWS.Lambda.Powertools.Common.csproj" Condition="'$(Configuration)'=='Debug'"/>
2021
</ItemGroup>
2122

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Linq;
18+
using System.Reflection;
19+
using System.Text;
20+
using System.Threading.Tasks;
21+
using AspectInjector.Broker;
22+
using AWS.Lambda.Powertools.Common;
23+
using AWS.Lambda.Powertools.Common.Utils;
24+
25+
namespace AWS.Lambda.Powertools.Tracing.Internal;
26+
27+
/// <summary>
28+
/// Tracing Aspect
29+
/// Scope.Global is singleton
30+
/// </summary>
31+
[Aspect(Scope.Global, Factory = typeof(TracingAspectFactory))]
32+
public class TracingAspect
33+
{
34+
/// <summary>
35+
/// The Powertools for AWS Lambda (.NET) configurations
36+
/// </summary>
37+
private readonly IPowertoolsConfigurations _powertoolsConfigurations;
38+
39+
/// <summary>
40+
/// X-Ray Recorder
41+
/// </summary>
42+
private readonly IXRayRecorder _xRayRecorder;
43+
44+
/// <summary>
45+
/// If true, then is cold start
46+
/// </summary>
47+
private static bool _isColdStart = true;
48+
49+
/// <summary>
50+
/// If true, capture annotations
51+
/// </summary>
52+
private static bool _captureAnnotations = true;
53+
54+
/// <summary>
55+
/// If true, annotations have been captured
56+
/// </summary>
57+
private bool _isAnnotationsCaptured;
58+
59+
/// <summary>
60+
/// Aspect constructor
61+
/// </summary>
62+
/// <param name="powertoolsConfigurations"></param>
63+
/// <param name="xRayRecorder"></param>
64+
public TracingAspect(IPowertoolsConfigurations powertoolsConfigurations, IXRayRecorder xRayRecorder)
65+
{
66+
_powertoolsConfigurations = powertoolsConfigurations;
67+
_xRayRecorder = xRayRecorder;
68+
}
69+
70+
/// <summary>
71+
/// Surrounds the specific method with Tracing aspect
72+
/// </summary>
73+
/// <param name="instance"></param>
74+
/// <param name="name"></param>
75+
/// <param name="args"></param>
76+
/// <param name="hostType"></param>
77+
/// <param name="method"></param>
78+
/// <param name="returnType"></param>
79+
/// <param name="target"></param>
80+
/// <param name="triggers"></param>
81+
/// <returns></returns>
82+
[Advice(Kind.Around)]
83+
public object Around(
84+
[Argument(Source.Instance)] object instance,
85+
[Argument(Source.Name)] string name,
86+
[Argument(Source.Arguments)] object[] args,
87+
[Argument(Source.Type)] Type hostType,
88+
[Argument(Source.Metadata)] MethodBase method,
89+
[Argument(Source.ReturnType)] Type returnType,
90+
[Argument(Source.Target)] Func<object[], object> target,
91+
[Argument(Source.Triggers)] Attribute[] triggers)
92+
{
93+
var trigger = triggers.OfType<TracingAttribute>().First();
94+
95+
if (TracingDisabled())
96+
return target(args);
97+
98+
var @namespace = !string.IsNullOrWhiteSpace(trigger.Namespace) ? trigger.Namespace : _powertoolsConfigurations.Service;
99+
100+
var (segmentName, metadataName) = string.IsNullOrWhiteSpace(trigger.SegmentName)
101+
? ($"## {name}", name)
102+
: (trigger.SegmentName, trigger.SegmentName);
103+
104+
BeginSegment(segmentName, @namespace);
105+
106+
try
107+
{
108+
var result = target(args);
109+
110+
if (result is Task task)
111+
{
112+
return WrapTask(task, metadataName, trigger.CaptureMode, @namespace);
113+
}
114+
115+
HandleResponse(metadataName, result, trigger.CaptureMode, @namespace);
116+
_xRayRecorder.EndSubsegment(); // End segment here for sync methods
117+
return result;
118+
}
119+
catch (Exception ex)
120+
{
121+
HandleException(ex, metadataName, trigger.CaptureMode, @namespace);
122+
_xRayRecorder.EndSubsegment(); // End segment here for sync methods with exceptions
123+
throw;
124+
}
125+
}
126+
127+
private object WrapTask(Task task, string name, TracingCaptureMode captureMode, string @namespace)
128+
{
129+
if (task.GetType() == typeof(Task))
130+
{
131+
return WrapVoidTask(task, name, captureMode, @namespace);
132+
}
133+
134+
// Create an async wrapper task that returns the original task type
135+
async Task AsyncWrapper()
136+
{
137+
try
138+
{
139+
await task;
140+
var result = task.GetType().GetProperty("Result")?.GetValue(task);
141+
HandleResponse(name, result, captureMode, @namespace);
142+
}
143+
catch (Exception ex)
144+
{
145+
HandleException(ex, name, captureMode, @namespace);
146+
throw;
147+
}
148+
finally
149+
{
150+
_xRayRecorder.EndSubsegment();
151+
}
152+
}
153+
154+
// Start the wrapper and return original task
155+
_ = AsyncWrapper();
156+
return task;
157+
}
158+
159+
private async Task WrapVoidTask(Task task, string name, TracingCaptureMode captureMode, string @namespace)
160+
{
161+
try
162+
{
163+
await task;
164+
HandleResponse(name, null, captureMode, @namespace);
165+
}
166+
catch (Exception ex)
167+
{
168+
HandleException(ex, name, captureMode, @namespace);
169+
throw;
170+
}
171+
finally
172+
{
173+
_xRayRecorder.EndSubsegment();
174+
}
175+
}
176+
177+
private void BeginSegment(string segmentName, string @namespace)
178+
{
179+
_xRayRecorder.BeginSubsegment(segmentName);
180+
_xRayRecorder.SetNamespace(@namespace);
181+
182+
if (_captureAnnotations)
183+
{
184+
_xRayRecorder.AddAnnotation("ColdStart", _isColdStart);
185+
186+
_captureAnnotations = false;
187+
_isAnnotationsCaptured = true;
188+
189+
if (_powertoolsConfigurations.IsServiceDefined)
190+
_xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service);
191+
}
192+
193+
_isColdStart = false;
194+
}
195+
196+
private void HandleResponse(string segmentName, object result, TracingCaptureMode captureMode, string @namespace)
197+
{
198+
if (!CaptureResponse(captureMode)) return;
199+
#if NET8_0_OR_GREATER
200+
if (!RuntimeFeatureWrapper.IsDynamicCodeSupported) // is AOT
201+
{
202+
_xRayRecorder.AddMetadata(
203+
@namespace,
204+
$"{segmentName} response",
205+
Serializers.PowertoolsTracingSerializer.Serialize(result)
206+
);
207+
return;
208+
}
209+
#endif
210+
211+
_xRayRecorder.AddMetadata(
212+
@namespace,
213+
$"{segmentName} response",
214+
result
215+
);
216+
}
217+
218+
/// <summary>
219+
/// When Aspect Handler exits runs this code to end subsegment
220+
/// </summary>
221+
[Advice(Kind.After)]
222+
public void OnExit()
223+
{
224+
if (TracingDisabled())
225+
return;
226+
227+
if (_isAnnotationsCaptured)
228+
_captureAnnotations = true;
229+
}
230+
231+
private bool TracingDisabled()
232+
{
233+
if (_powertoolsConfigurations.TracingDisabled)
234+
{
235+
Console.WriteLine("Tracing has been disabled via env var POWERTOOLS_TRACE_DISABLED");
236+
return true;
237+
}
238+
239+
if (!_powertoolsConfigurations.IsLambdaEnvironment)
240+
{
241+
Console.WriteLine("Running outside Lambda environment; disabling Tracing");
242+
return true;
243+
}
244+
245+
return false;
246+
}
247+
248+
private bool CaptureResponse(TracingCaptureMode captureMode)
249+
{
250+
if (TracingDisabled())
251+
return false;
252+
253+
return captureMode switch
254+
{
255+
TracingCaptureMode.EnvironmentVariable => _powertoolsConfigurations.TracerCaptureResponse,
256+
TracingCaptureMode.Response => true,
257+
TracingCaptureMode.ResponseAndError => true,
258+
_ => false
259+
};
260+
}
261+
262+
private bool CaptureError(TracingCaptureMode captureMode)
263+
{
264+
if (TracingDisabled())
265+
return false;
266+
267+
return captureMode switch
268+
{
269+
TracingCaptureMode.EnvironmentVariable => _powertoolsConfigurations.TracerCaptureError,
270+
TracingCaptureMode.Error => true,
271+
TracingCaptureMode.ResponseAndError => true,
272+
_ => false
273+
};
274+
}
275+
276+
private void HandleException(Exception exception, string name, TracingCaptureMode captureMode, string @namespace)
277+
{
278+
if (!CaptureError(captureMode)) return;
279+
280+
var nameSpace = @namespace;
281+
var sb = new StringBuilder();
282+
sb.AppendLine($"Exception type: {exception.GetType()}");
283+
sb.AppendLine($"Exception message: {exception.Message}");
284+
sb.AppendLine($"Stack trace: {exception.StackTrace}");
285+
286+
if (exception.InnerException != null)
287+
{
288+
sb.AppendLine("---BEGIN InnerException--- ");
289+
sb.AppendLine($"Exception type {exception.InnerException.GetType()}");
290+
sb.AppendLine($"Exception message: {exception.InnerException.Message}");
291+
sb.AppendLine($"Stack trace: {exception.InnerException.StackTrace}");
292+
sb.AppendLine("---END Inner Exception");
293+
}
294+
295+
_xRayRecorder.AddMetadata(
296+
nameSpace,
297+
$"{name} error",
298+
sb.ToString()
299+
);
300+
}
301+
302+
/// <summary>
303+
/// Resets static variables for test.
304+
/// </summary>
305+
internal static void ResetForTest()
306+
{
307+
_isColdStart = true;
308+
_captureAnnotations = true;
309+
}
310+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
17+
using System;
18+
using AWS.Lambda.Powertools.Common;
19+
20+
namespace AWS.Lambda.Powertools.Tracing.Internal;
21+
22+
internal static class TracingAspectFactory
23+
{
24+
/// <summary>
25+
/// Get an instance of the TracingAspect class.
26+
/// </summary>
27+
/// <param name="type">The type of the class to be logged.</param>
28+
/// <returns>An instance of the TracingAspect class.</returns>
29+
public static object GetInstance(Type type)
30+
{
31+
return new TracingAspect(PowertoolsConfigurations.Instance, XRayRecorder.Instance);
32+
}
33+
}

0 commit comments

Comments
 (0)