Skip to content
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

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

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;
}
}
64 changes: 61 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,57 @@ 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();
var currentEnvValue = GetEnvironmentVariable(envName);
var assemblyName = ParseAssemblyName(_powertoolsEnvironment.GetAssemblyName(type));

// If there is an existing execution environment variable add the annotations package as a suffix.
if(!string.IsNullOrEmpty(currentEnvValue))
{
// Avoid duplication - should not happen since the calling Instances are Singletons - defensive purposes
if (currentEnvValue.Contains(assemblyName))
{
return;
}

envValue.Append($"{currentEnvValue} ");
}

var assemblyVersion = _powertoolsEnvironment.GetAssemblyVersion(type);

envValue.Append($"{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
Loading