Skip to content

Commit 739ff1e

Browse files
authored
Merge branch 'develop' into dependabot/github_actions/actions/setup-dotnet-4.3.1
2 parents c0d343f + 8b6d925 commit 739ff1e

File tree

20 files changed

+512
-78
lines changed

20 files changed

+512
-78
lines changed

libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@ namespace AWS.Lambda.Powertools.Common;
2020
/// </summary>
2121
internal static class Constants
2222
{
23+
/// <summary>
24+
/// Constant for AWS_LAMBDA_INITIALIZATION_TYPE environment variable
25+
/// This is used to determine if the Lambda function is running in provisioned concurrency mode
26+
/// or not. If the value is "provisioned-concurrency", it indicates that the function is running in provisioned
27+
/// concurrency mode. Otherwise, it is running in standard mode.
28+
/// </summary>
29+
internal const string AWSInitializationTypeEnv = "AWS_LAMBDA_INITIALIZATION_TYPE";
30+
2331
/// <summary>
2432
/// Constant for POWERTOOLS_SERVICE_NAME environment variable
2533
/// </summary>

libraries/src/AWS.Lambda.Powertools.Common/Core/IPowertoolsConfigurations.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,4 +167,15 @@ public interface IPowertoolsConfigurations
167167
/// Gets a value indicating whether Metrics are disabled.
168168
/// </summary>
169169
bool MetricsDisabled { get; }
170+
171+
/// <summary>
172+
/// Indicates if the current execution is a cold start.
173+
/// </summary>
174+
bool IsColdStart { get; }
175+
176+
/// <summary>
177+
/// AWS Lambda initialization type.
178+
/// This is set to "on-demand" for on-demand Lambda functions and "provisioned-concurrency" for provisioned concurrency.
179+
/// </summary>
180+
string AwsInitializationType { get; }
170181
}

libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,4 @@ public interface ISystemWrapper
5959
/// </summary>
6060
/// <param name="type"></param>
6161
void SetExecutionEnvironment<T>(T type);
62-
63-
/// <summary>
64-
/// Sets console output
65-
/// Useful for testing and checking the console output
66-
/// <code>
67-
/// var consoleOut = new StringWriter();
68-
/// SystemWrapper.Instance.SetOut(consoleOut);
69-
/// </code>
70-
/// </summary>
71-
/// <param name="writeTo">The TextWriter instance where to write to</param>
72-
void SetOut(TextWriter writeTo);
7362
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
using System;
2+
using System.Threading;
3+
4+
namespace AWS.Lambda.Powertools.Common.Core;
5+
6+
/// <summary>
7+
/// Tracks Lambda lifecycle state including cold starts
8+
/// </summary>
9+
internal static class LambdaLifecycleTracker
10+
{
11+
// Static flag that's true only for the first Lambda container initialization
12+
private static bool _isFirstContainer = true;
13+
14+
// Store the cold start state for the current invocation
15+
private static readonly AsyncLocal<bool?> CurrentInvocationColdStart = new AsyncLocal<bool?>();
16+
17+
private static string _lambdaInitType;
18+
private static string LambdaInitType => _lambdaInitType ?? Environment.GetEnvironmentVariable(Constants.AWSInitializationTypeEnv);
19+
20+
/// <summary>
21+
/// Returns true if the current Lambda invocation is a cold start
22+
/// </summary>
23+
public static bool IsColdStart
24+
{
25+
get
26+
{
27+
if(LambdaInitType == "provisioned-concurrency")
28+
{
29+
// If the Lambda is provisioned concurrency, it is not a cold start
30+
return false;
31+
}
32+
33+
// Initialize the cold start state for this invocation if not already set
34+
if (!CurrentInvocationColdStart.Value.HasValue)
35+
{
36+
// Capture the container's cold start state for this entire invocation
37+
CurrentInvocationColdStart.Value = _isFirstContainer;
38+
39+
// After detecting the first invocation, mark future ones as warm
40+
if (_isFirstContainer)
41+
{
42+
_isFirstContainer = false;
43+
}
44+
}
45+
46+
// Return the cold start state for this invocation (cannot change during the invocation)
47+
return CurrentInvocationColdStart.Value ?? false;
48+
}
49+
}
50+
51+
52+
53+
/// <summary>
54+
/// Resets the cold start state for testing
55+
/// </summary>
56+
/// <param name="resetContainer">Whether to reset the container state (defaults to true)</param>
57+
internal static void Reset(bool resetContainer = true)
58+
{
59+
if (resetContainer)
60+
{
61+
_isFirstContainer = true;
62+
}
63+
CurrentInvocationColdStart.Value = null;
64+
_lambdaInitType = null;
65+
}
66+
}

libraries/src/AWS.Lambda.Powertools.Common/Core/PowertoolsConfigurations.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
*/
1515

1616
using System.Globalization;
17+
using AWS.Lambda.Powertools.Common.Core;
1718

1819
namespace AWS.Lambda.Powertools.Common;
1920

@@ -222,4 +223,11 @@ public void SetExecutionEnvironment<T>(T type)
222223

223224
/// <inheritdoc />
224225
public bool MetricsDisabled => GetEnvironmentVariableOrDefault(Constants.PowertoolsMetricsDisabledEnv, false);
226+
227+
/// <inheritdoc />
228+
public bool IsColdStart => LambdaLifecycleTracker.IsColdStart;
229+
230+
/// <inheritdoc />
231+
public string AwsInitializationType =>
232+
GetEnvironmentVariable(Constants.AWSInitializationTypeEnv);
225233
}

libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs

Lines changed: 68 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ namespace AWS.Lambda.Powertools.Common;
2727
public class SystemWrapper : ISystemWrapper
2828
{
2929
private static IPowertoolsEnvironment _powertoolsEnvironment;
30+
private static bool _inTestMode = false;
31+
private static TextWriter _testOutputStream;
32+
private static bool _outputResetPerformed = false;
3033

3134
/// <summary>
3235
/// The instance
@@ -41,13 +44,11 @@ public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
4144
_powertoolsEnvironment = powertoolsEnvironment;
4245
_instance ??= this;
4346

44-
// Clear AWS SDK Console injected parameters StdOut and StdErr
45-
var standardOutput = new StreamWriter(Console.OpenStandardOutput());
46-
standardOutput.AutoFlush = true;
47-
Console.SetOut(standardOutput);
48-
var errordOutput = new StreamWriter(Console.OpenStandardError());
49-
errordOutput.AutoFlush = true;
50-
Console.SetError(errordOutput);
47+
if (!_inTestMode)
48+
{
49+
// Clear AWS SDK Console injected parameters in production only
50+
ResetConsoleOutput();
51+
}
5152
}
5253

5354
/// <summary>
@@ -72,7 +73,15 @@ public string GetEnvironmentVariable(string variable)
7273
/// <param name="value">The value.</param>
7374
public void Log(string value)
7475
{
75-
Console.Write(value);
76+
if (_inTestMode && _testOutputStream != null)
77+
{
78+
_testOutputStream.Write(value);
79+
}
80+
else
81+
{
82+
EnsureConsoleOutputOnce();
83+
Console.Write(value);
84+
}
7685
}
7786

7887
/// <summary>
@@ -81,7 +90,15 @@ public void Log(string value)
8190
/// <param name="value">The value.</param>
8291
public void LogLine(string value)
8392
{
84-
Console.WriteLine(value);
93+
if (_inTestMode && _testOutputStream != null)
94+
{
95+
_testOutputStream.WriteLine(value);
96+
}
97+
else
98+
{
99+
EnsureConsoleOutputOnce();
100+
Console.WriteLine(value);
101+
}
85102
}
86103

87104
/// <summary>
@@ -126,9 +143,20 @@ public void SetExecutionEnvironment<T>(T type)
126143
SetEnvironmentVariable(envName, envValue.ToString());
127144
}
128145

129-
/// <inheritdoc />
130-
public void SetOut(TextWriter writeTo)
146+
/// <summary>
147+
/// Sets console output
148+
/// Useful for testing and checking the console output
149+
/// <code>
150+
/// var consoleOut = new StringWriter();
151+
/// SystemWrapper.Instance.SetOut(consoleOut);
152+
/// </code>
153+
/// </summary>
154+
/// <param name="writeTo">The TextWriter instance where to write to</param>
155+
156+
public static void SetOut(TextWriter writeTo)
131157
{
158+
_testOutputStream = writeTo;
159+
_inTestMode = true;
132160
Console.SetOut(writeTo);
133161
}
134162

@@ -152,4 +180,33 @@ private string ParseAssemblyName(string assemblyName)
152180

153181
return $"{Constants.FeatureContextIdentifier}/{assemblyName}";
154182
}
183+
184+
private static void EnsureConsoleOutputOnce()
185+
{
186+
if (_outputResetPerformed) return;
187+
ResetConsoleOutput();
188+
_outputResetPerformed = true;
189+
}
190+
191+
private static void ResetConsoleOutput()
192+
{
193+
var standardOutput = new StreamWriter(Console.OpenStandardOutput());
194+
standardOutput.AutoFlush = true;
195+
Console.SetOut(standardOutput);
196+
var errorOutput = new StreamWriter(Console.OpenStandardError());
197+
errorOutput.AutoFlush = true;
198+
Console.SetError(errorOutput);
199+
}
200+
201+
public static void ClearOutputResetFlag()
202+
{
203+
_outputResetPerformed = false;
204+
}
205+
206+
// For test cleanup
207+
internal static void ResetTestMode()
208+
{
209+
_inTestMode = false;
210+
_testOutputStream = null;
211+
}
155212
}

libraries/src/AWS.Lambda.Powertools.Common/InternalsVisibleTo.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Logging")]
1919
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Metrics")]
20+
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Tracing")]
2021
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Idempotency")]
2122
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Common.Tests")]
2223
[assembly: InternalsVisibleTo("AWS.Lambda.Powertools.Tracing.Tests")]

libraries/src/AWS.Lambda.Powertools.Logging/Internal/LoggingAspect.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using System.Text.Json;
2222
using AspectInjector.Broker;
2323
using AWS.Lambda.Powertools.Common;
24+
using AWS.Lambda.Powertools.Common.Core;
2425
using AWS.Lambda.Powertools.Logging.Serializers;
2526
using Microsoft.Extensions.Logging;
2627

@@ -34,11 +35,6 @@ namespace AWS.Lambda.Powertools.Logging.Internal;
3435
[Aspect(Scope.Global, Factory = typeof(LoggingAspectFactory))]
3536
public class LoggingAspect
3637
{
37-
/// <summary>
38-
/// The is cold start
39-
/// </summary>
40-
private bool _isColdStart = true;
41-
4238
/// <summary>
4339
/// The initialize context
4440
/// </summary>
@@ -143,9 +139,8 @@ public void OnEntry(
143139
if (!_initializeContext)
144140
return;
145141

146-
Logger.AppendKey(LoggingConstants.KeyColdStart, _isColdStart);
142+
Logger.AppendKey(LoggingConstants.KeyColdStart, LambdaLifecycleTracker.IsColdStart);
147143

148-
_isColdStart = false;
149144
_initializeContext = false;
150145
_isContextInitialized = true;
151146

libraries/src/AWS.Lambda.Powertools.Metrics/Internal/MetricsAspect.cs

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using Amazon.Lambda.Core;
2121
using AspectInjector.Broker;
2222
using AWS.Lambda.Powertools.Common;
23+
using AWS.Lambda.Powertools.Common.Core;
2324

2425
namespace AWS.Lambda.Powertools.Metrics;
2526

@@ -30,22 +31,12 @@ namespace AWS.Lambda.Powertools.Metrics;
3031
[Aspect(Scope.Global)]
3132
public class MetricsAspect
3233
{
33-
/// <summary>
34-
/// The is cold start
35-
/// </summary>
36-
private static bool _isColdStart;
37-
3834
/// <summary>
3935
/// Gets the metrics instance.
4036
/// </summary>
4137
/// <value>The metrics instance.</value>
4238
private static IMetrics _metricsInstance;
4339

44-
static MetricsAspect()
45-
{
46-
_isColdStart = true;
47-
}
48-
4940
/// <summary>
5041
/// Runs before the execution of the method marked with the Metrics Attribute
5142
/// </summary>
@@ -89,10 +80,9 @@ public void Before(
8980
Triggers = triggers
9081
};
9182

92-
if (_isColdStart)
83+
if (LambdaLifecycleTracker.IsColdStart)
9384
{
9485
_metricsInstance.CaptureColdStartMetric(GetContext(eventArgs));
95-
_isColdStart = false;
9686
}
9787
}
9888

@@ -112,7 +102,7 @@ public void Exit()
112102
internal static void ResetForTest()
113103
{
114104
_metricsInstance = null;
115-
_isColdStart = true;
105+
LambdaLifecycleTracker.Reset();
116106
Metrics.ResetForTest();
117107
}
118108

libraries/src/AWS.Lambda.Powertools.Tracing/Internal/TracingAspect.cs

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
using System.Threading.Tasks;
2121
using AspectInjector.Broker;
2222
using AWS.Lambda.Powertools.Common;
23+
using AWS.Lambda.Powertools.Common.Core;
2324
using AWS.Lambda.Powertools.Common.Utils;
2425

2526
namespace AWS.Lambda.Powertools.Tracing.Internal;
@@ -41,11 +42,6 @@ public class TracingAspect
4142
/// </summary>
4243
private readonly IXRayRecorder _xRayRecorder;
4344

44-
/// <summary>
45-
/// If true, then is cold start
46-
/// </summary>
47-
private static bool _isColdStart = true;
48-
4945
/// <summary>
5046
/// If true, capture annotations
5147
/// </summary>
@@ -148,16 +144,14 @@ private void BeginSegment(string segmentName, string @namespace)
148144

149145
if (_captureAnnotations)
150146
{
151-
_xRayRecorder.AddAnnotation("ColdStart", _isColdStart);
147+
_xRayRecorder.AddAnnotation("ColdStart", LambdaLifecycleTracker.IsColdStart);
152148

153149
_captureAnnotations = false;
154150
_isAnnotationsCaptured = true;
155151

156152
if (_powertoolsConfigurations.IsServiceDefined)
157153
_xRayRecorder.AddAnnotation("Service", _powertoolsConfigurations.Service);
158154
}
159-
160-
_isColdStart = false;
161155
}
162156

163157
private void HandleResponse(string name, object result, TracingCaptureMode captureMode, string @namespace)
@@ -253,7 +247,7 @@ private bool CaptureError(TracingCaptureMode captureMode)
253247

254248
internal static void ResetForTest()
255249
{
256-
_isColdStart = true;
250+
LambdaLifecycleTracker.Reset();
257251
_captureAnnotations = true;
258252
}
259253
}

0 commit comments

Comments
 (0)