Skip to content

feat: Powertools User-Agent for Lambda execution environment #246

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

Merged
merged 7 commits into from
May 3, 2023
10 changes: 10 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Common/Core/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,14 @@ internal static class Constants
/// Constant for LAMBDA_TASK_ROOT environment variable
/// </summary>
internal const string LambdaTaskRoot = "LAMBDA_TASK_ROOT";

/// <summary>
/// Constant for AWS_EXECUTION_ENV environment variable
/// </summary>
internal const string AwsExecutionEnvironmentVariableName = "AWS_EXECUTION_ENV";

/// <summary>
/// Constant for Powertools feature identifier fo AWS_EXECUTION_ENV environment variable
/// </summary>
internal const string FeatureContextIdentifier = "PT";
}
Original file line number Diff line number Diff line change
Expand Up @@ -120,4 +120,10 @@ public interface IPowertoolsConfigurations
/// <param name="defaultValue">if set to <c>true</c> [default value].</param>
/// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue);

/// <summary>
/// Sets the execution Environment Variable (AWS_EXECUTION_ENV)
/// </summary>
/// <param name="type"></param>
void SetExecutionEnvironment<T>(T type);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
namespace AWS.Lambda.Powertools.Common;

/// <summary>
/// Interface for PowertoolsEnvironment
/// </summary>
public interface IPowertoolsEnvironment
{
/// <summary>
/// Get environment variable by variable name
/// </summary>
/// <param name="variableName"></param>
/// <returns>Environment variable</returns>
string GetEnvironmentVariable(string variableName);

/// <summary>
/// Set environment variable
/// </summary>
/// <param name="variableName"></param>
/// <param name="value">Setting this to null will remove environment variable with that name</param>
void SetEnvironmentVariable(string variableName, string value);

/// <summary>
/// Get the calling Type Assembly Name
/// </summary>
/// <param name="type"></param>
/// <typeparam name="T"></typeparam>
/// <returns>Assembly Name</returns>
string GetAssemblyName<T>(T type);

/// <summary>
/// Get the calling Type Assembly Version
/// </summary>
/// <param name="type"></param>
/// <typeparam name="T"></typeparam>
/// <returns>Assembly Version in the Major.Minor.Build format</returns>
string GetAssemblyVersion<T>(T type);
}
13 changes: 13 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,17 @@ public interface ISystemWrapper
/// </summary>
/// <returns>System.Double.</returns>
double GetRandom();

/// <summary>
/// Sets the environment variable.
/// </summary>
/// <param name="variable">The variable.</param>
/// <param name="value"></param>
void SetEnvironmentVariable(string variable, string value);

/// <summary>
/// Sets the execution Environment Variable (AWS_EXECUTION_ENV)
/// </summary>
/// <param name="type"></param>
void SetExecutionEnvironment<T>(T type);
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,4 +184,10 @@ public bool GetEnvironmentVariableOrDefault(string variable, bool defaultValue)
/// <value><c>true</c> if [tracing is disabled]; otherwise, <c>false</c>.</value>
public bool TracingDisabled =>
GetEnvironmentVariableOrDefault(Constants.TracingDisabledEnv, false);

/// <inheritdoc />
public void SetExecutionEnvironment<T>(T type)
{
_systemWrapper.SetExecutionEnvironment(type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This one calls SystemWrapper. SetExecutionEnvironment and the system Wrapper I believe calls PowertoolsEnvironment, I believe there are too many layers of abstractions.

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System;

namespace AWS.Lambda.Powertools.Common;

/// <inheritdoc />
public class PowertoolsEnvironment : IPowertoolsEnvironment
{
/// <summary>
/// The instance
/// </summary>
private static IPowertoolsEnvironment _instance;

/// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static IPowertoolsEnvironment Instance => _instance ??= new PowertoolsEnvironment();

/// <inheritdoc />
public string GetEnvironmentVariable(string variableName)
{
return Environment.GetEnvironmentVariable(variableName);
}

/// <inheritdoc />
public void SetEnvironmentVariable(string variableName, string value)
{
Environment.SetEnvironmentVariable(variableName, value);
}

/// <inheritdoc />
public string GetAssemblyName<T>(T type)
{
return type.GetType().Assembly.GetName().Name;
}

/// <inheritdoc />
public string GetAssemblyVersion<T>(T type)
{
var version = type.GetType().Assembly.GetName().Version;
return version != null ? $"{version.Major}.{version.Minor}.{version.Build}" : string.Empty;
}
}
58 changes: 55 additions & 3 deletions libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
*/

using System;
using System.Text;

namespace AWS.Lambda.Powertools.Common;

Expand All @@ -24,6 +25,8 @@ namespace AWS.Lambda.Powertools.Common;
/// <seealso cref="ISystemWrapper" />
public class SystemWrapper : ISystemWrapper
{
private static IPowertoolsEnvironment _powertoolsEnvironment;

/// <summary>
/// The instance
/// </summary>
Expand All @@ -32,15 +35,17 @@ public class SystemWrapper : ISystemWrapper
/// <summary>
/// Prevents a default instance of the <see cref="SystemWrapper" /> class from being created.
/// </summary>
private SystemWrapper()
public SystemWrapper(IPowertoolsEnvironment powertoolsEnvironment)
{
_powertoolsEnvironment = powertoolsEnvironment;
_instance ??= this;
}

/// <summary>
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static ISystemWrapper Instance => _instance ??= new SystemWrapper();
public static ISystemWrapper Instance => _instance ??= new SystemWrapper(PowertoolsEnvironment.Instance);

/// <summary>
/// Gets the environment variable.
Expand All @@ -49,7 +54,7 @@ private SystemWrapper()
/// <returns>System.String.</returns>
public string GetEnvironmentVariable(string variable)
{
return Environment.GetEnvironmentVariable(variable);
return _powertoolsEnvironment.GetEnvironmentVariable(variable);
}

/// <summary>
Expand Down Expand Up @@ -78,4 +83,51 @@ public double GetRandom()
{
return new Random().NextDouble();
}

/// <inheritdoc />
public void SetEnvironmentVariable(string variable, string value)
{
_powertoolsEnvironment.SetEnvironmentVariable(variable, value);
}

/// <inheritdoc />
public void SetExecutionEnvironment<T>(T type)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the whole system so it might already being doing this but make sure this is only called once per power tools assembly. These assembly look up calls are can be costly and we don't want to it for every request.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the feedback @normj . The implementation prevents multiple instances of SystemWrapper to be created. The instances that create systemwrapper are singletons. The constructors are called once for the lambda lifetime.

{
const string envName = Constants.AwsExecutionEnvironmentVariableName;

var envValue = new StringBuilder();

// If there is an existing execution environment variable add the annotations package as a suffix.
if(!string.IsNullOrEmpty(GetEnvironmentVariable(envName)))
{
envValue.Append($"{GetEnvironmentVariable(envName)} ");
}

var assemblyVersion = _powertoolsEnvironment.GetAssemblyVersion(type);
var assemblyName =_powertoolsEnvironment.GetAssemblyName(type);

envValue.Append($"{ParseAssemblyName(assemblyName)}/{assemblyVersion}");

SetEnvironmentVariable(envName, envValue.ToString());
}

/// <summary>
/// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version)
/// Fallback to Assembly Name on exception
/// </summary>
/// <param name="assemblyName"></param>
/// <returns></returns>
private string ParseAssemblyName(string assemblyName)
{
try
{
var parsedName = assemblyName.Substring(assemblyName.LastIndexOf(".", StringComparison.Ordinal)+1);
return $"{Constants.FeatureContextIdentifier}/{parsedName}";
}
catch
{
//NOOP
}
return $"{Constants.FeatureContextIdentifier}/{assemblyName}";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ public PowertoolsLogger(
{
(_name, _powertoolsConfigurations, _systemWrapper, _getCurrentConfig) = (name,
powertoolsConfigurations, systemWrapper, getCurrentConfig);

_powertoolsConfigurations.SetExecutionEnvironment(this);
}

/// <summary>
Expand Down
2 changes: 2 additions & 0 deletions libraries/src/AWS.Lambda.Powertools.Metrics/Metrics.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ internal Metrics(IPowertoolsConfigurations powertoolsConfigurations, string name
_raiseOnEmptyMetrics = raiseOnEmptyMetrics;
_captureColdStartEnabled = captureColdStartEnabled;
_context = InitializeContext(nameSpace, service, null);

_powertoolsConfigurations.SetExecutionEnvironment(this);
}

/// <summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
using Amazon.XRay.Recorder.Core.Internal.Emitters;
using Amazon.XRay.Recorder.Core.Internal.Entities;
using Amazon.XRay.Recorder.Core.Strategies;
using AWS.Lambda.Powertools.Common;

namespace AWS.Lambda.Powertools.Tracing.Internal;

Expand All @@ -28,6 +29,9 @@ namespace AWS.Lambda.Powertools.Tracing.Internal;
/// <seealso cref="IXRayRecorder" />
internal class XRayRecorder : IXRayRecorder
{
private static IAWSXRayRecorder _awsxRayRecorder;
private static IPowertoolsConfigurations _powertoolsConfigurations;

/// <summary>
/// The instance
/// </summary>
Expand All @@ -37,25 +41,34 @@ internal class XRayRecorder : IXRayRecorder
/// Gets the instance.
/// </summary>
/// <value>The instance.</value>
public static IXRayRecorder Instance => _instance ??= new XRayRecorder();
public static IXRayRecorder Instance => _instance ??= new XRayRecorder(AWSXRayRecorder.Instance, PowertoolsConfigurations.Instance);

public XRayRecorder(IAWSXRayRecorder awsxRayRecorder, IPowertoolsConfigurations powertoolsConfigurations)
{
_instance = this;
_powertoolsConfigurations = powertoolsConfigurations;
_powertoolsConfigurations.SetExecutionEnvironment(this);
_isLambda = _powertoolsConfigurations.IsLambdaEnvironment;
_awsxRayRecorder = awsxRayRecorder;
}

/// <summary>
/// Checks whether current execution is in AWS Lambda.
/// </summary>
/// <returns>Returns true if current execution is in AWS Lambda.</returns>
private static readonly bool _isLambda = AWSXRayRecorder.IsLambda();
private static bool _isLambda;

/// <summary>
/// Gets the emitter.
/// </summary>
/// <value>The emitter.</value>
public ISegmentEmitter Emitter => _isLambda ? AWSXRayRecorder.Instance.Emitter : null;
public ISegmentEmitter Emitter => _isLambda ? _awsxRayRecorder.Emitter : null;

/// <summary>
/// Gets the streaming strategy.
/// </summary>
/// <value>The streaming strategy.</value>
public IStreamingStrategy StreamingStrategy => _isLambda ? AWSXRayRecorder.Instance.StreamingStrategy : null;
public IStreamingStrategy StreamingStrategy => _isLambda ? _awsxRayRecorder.StreamingStrategy : null;

/// <summary>
/// Begins the subsegment.
Expand All @@ -64,7 +77,7 @@ internal class XRayRecorder : IXRayRecorder
public void BeginSubsegment(string name)
{
if (_isLambda)
AWSXRayRecorder.Instance.BeginSubsegment(name);
_awsxRayRecorder.BeginSubsegment(name);
}

/// <summary>
Expand All @@ -74,7 +87,7 @@ public void BeginSubsegment(string name)
public void SetNamespace(string value)
{
if (_isLambda)
AWSXRayRecorder.Instance.SetNamespace(value);
_awsxRayRecorder.SetNamespace(value);
}

/// <summary>
Expand All @@ -85,7 +98,7 @@ public void SetNamespace(string value)
public void AddAnnotation(string key, object value)
{
if (_isLambda)
AWSXRayRecorder.Instance.AddAnnotation(key, value);
_awsxRayRecorder.AddAnnotation(key, value);
}

/// <summary>
Expand All @@ -97,7 +110,7 @@ public void AddAnnotation(string key, object value)
public void AddMetadata(string nameSpace, string key, object value)
{
if (_isLambda)
AWSXRayRecorder.Instance.AddMetadata(nameSpace, key, value);
_awsxRayRecorder.AddMetadata(nameSpace, key, value);
}

/// <summary>
Expand All @@ -106,7 +119,7 @@ public void AddMetadata(string nameSpace, string key, object value)
public void EndSubsegment()
{
if (_isLambda)
AWSXRayRecorder.Instance.EndSubsegment();
_awsxRayRecorder.EndSubsegment();
}

/// <summary>
Expand All @@ -116,7 +129,7 @@ public void EndSubsegment()
public Entity GetEntity()
{
return _isLambda
? AWSXRayRecorder.Instance.GetEntity()
? _awsxRayRecorder.TraceContext.GetEntity()
: new Subsegment("Root");
}

Expand All @@ -127,7 +140,7 @@ public Entity GetEntity()
public void SetEntity(Entity entity)
{
if (_isLambda)
AWSXRayRecorder.Instance.SetEntity(entity);
_awsxRayRecorder.TraceContext.SetEntity(entity);
}

/// <summary>
Expand All @@ -137,7 +150,7 @@ public void SetEntity(Entity entity)
public void AddException(Exception exception)
{
if (_isLambda)
AWSXRayRecorder.Instance.AddException(exception);
_awsxRayRecorder.AddException(exception);
}

/// <summary>
Expand All @@ -148,6 +161,6 @@ public void AddException(Exception exception)
public void AddHttpInformation(string key, object value)
{
if (_isLambda)
AWSXRayRecorder.Instance.AddHttpInformation(key, value);
_awsxRayRecorder.AddHttpInformation(key, value);
}
}
Loading