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
+ }
0 commit comments