Skip to content

Commit

Permalink
Add support for configuring logging for Lambda functions.
Browse files Browse the repository at this point in the history
  • Loading branch information
normj committed Oct 9, 2024
1 parent 80c2ace commit 4a9bbc7
Show file tree
Hide file tree
Showing 5 changed files with 260 additions and 3 deletions.
2 changes: 1 addition & 1 deletion src/Amazon.Lambda.Tools/Amazon.Lambda.Tools.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
<Copyright>Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.</Copyright>
<Product>AWS Lambda Tools for .NET CLI</Product>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
<Version>5.10.7</Version>
<Version>5.11.0</Version>
</PropertyGroup>
<ItemGroup>
<Content Include="Resources\build-lambda-zip.exe">
Expand Down
19 changes: 18 additions & 1 deletion src/Amazon.Lambda.Tools/Commands/DeployFunctionCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ public class DeployFunctionCommand : UpdateFunctionConfigCommand

LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD,
LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD,
LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY
LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY,

LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT,
LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL,
LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL,
LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP,
});

public string Architecture { get; set; }
Expand Down Expand Up @@ -319,6 +324,13 @@ protected override async Task<bool> PerformActionAsync()
{
SubnetIds = this.GetStringValuesOrDefault(this.SubnetIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SUBNETS, false)?.ToList(),
SecurityGroupIds = this.GetStringValuesOrDefault(this.SecurityGroupIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SECURITY_GROUPS, false)?.ToList()
},
LoggingConfig = new LoggingConfig
{
LogFormat = this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false),
ApplicationLogLevel = this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false),
SystemLogLevel = this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL, false),
LogGroup = this.GetStringValueOrDefault(this.LogGroup, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false),
}
};

Expand Down Expand Up @@ -622,6 +634,11 @@ protected override void SaveConfigFile(JsonData data)
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD.ConfigFileKey, this.GetBoolValueOrDefault(this.UseContainerForBuild, LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD.ConfigFileKey, this.GetStringValueOrDefault(this.ContainerImageForBuild, LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY.ConfigFileKey, this.GetStringValueOrDefault(this.CodeMountDirectory, LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY, false));

data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT.ConfigFileKey, this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL.ConfigFileKey, this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL.ConfigFileKey, this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL, false));
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP.ConfigFileKey, this.GetStringValueOrDefault(this.LogGroup, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false));
}
}
}
82 changes: 81 additions & 1 deletion src/Amazon.Lambda.Tools/Commands/UpdateFunctionConfigCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,12 @@ public class UpdateFunctionConfigCommand : LambdaBaseCommand
LambdaDefinedCommandOptions.ARGUMENT_ENVIRONMENT_VARIABLES,
LambdaDefinedCommandOptions.ARGUMENT_APPEND_ENVIRONMENT_VARIABLES,
LambdaDefinedCommandOptions.ARGUMENT_KMS_KEY_ARN,
LambdaDefinedCommandOptions.ARGUMENT_APPLY_DEFAULTS_FOR_UPDATE_OBSOLETE
LambdaDefinedCommandOptions.ARGUMENT_APPLY_DEFAULTS_FOR_UPDATE_OBSOLETE,

LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT,
LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL,
LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL,
LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP,
});

public string FunctionName { get; set; }
Expand Down Expand Up @@ -88,6 +93,12 @@ public class UpdateFunctionConfigCommand : LambdaBaseCommand

public string FunctionUrlLink { get; private set; }

public string LogFormat { get; set; }
public string LogApplicationLevel { get; set; }
public string LogSystemLevel { get; set; }
public string LogGroup { get; set; }


public UpdateFunctionConfigCommand(IToolLogger logger, string workingDirectory, string[] args)
: base(logger, workingDirectory, UpdateCommandOptions, args)
{
Expand Down Expand Up @@ -161,6 +172,15 @@ protected override void ParseCommandArguments(CommandOptions values)
this.FunctionUrlEnable = tuple.Item2.BoolValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_AUTH.Switch)) != null)
this.FunctionUrlAuthType = tuple.Item2.StringValue;

if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT.Switch)) != null)
this.LogFormat = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL.Switch)) != null)
this.LogApplicationLevel = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL.Switch)) != null)
this.LogSystemLevel = tuple.Item2.StringValue;
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP.Switch)) != null)
this.LogGroup = tuple.Item2.StringValue;
}


Expand Down Expand Up @@ -623,6 +643,66 @@ private UpdateFunctionConfigurationRequest CreateConfigurationRequestIfDifferent
}
}

var logFormat = this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false);
if (!string.IsNullOrEmpty(logFormat))
{
if (request.LoggingConfig == null)
{
request.LoggingConfig = new LoggingConfig();
}

if (!string.Equals(request.LoggingConfig.LogFormat, existingConfiguration.LoggingConfig?.LogFormat, StringComparison.Ordinal))
{
request.LoggingConfig.LogFormat = logFormat;
different = true;
}
}

var logApplicationLevel = this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false);
if (!string.IsNullOrEmpty(logApplicationLevel))
{
if (request.LoggingConfig == null)
{
request.LoggingConfig = new LoggingConfig();
}

if (!string.Equals(request.LoggingConfig.ApplicationLogLevel, existingConfiguration.LoggingConfig?.ApplicationLogLevel, StringComparison.Ordinal))
{
request.LoggingConfig.ApplicationLogLevel = logApplicationLevel;
different = true;
}
}

var logSystemLevel = this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false);
if (!string.IsNullOrEmpty(logSystemLevel))
{
if (request.LoggingConfig == null)
{
request.LoggingConfig = new LoggingConfig();
}

if (!string.Equals(request.LoggingConfig.SystemLogLevel, existingConfiguration.LoggingConfig?.SystemLogLevel, StringComparison.Ordinal))
{
request.LoggingConfig.SystemLogLevel = logSystemLevel;
different = true;
}
}

var logGroup = this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false);
if (logGroup != null) // Allow empty string to reset back to Lambda's default log group.
{
if (request.LoggingConfig == null)
{
request.LoggingConfig = new LoggingConfig();
}

if (!string.Equals(request.LoggingConfig.LogGroup, existingConfiguration.LoggingConfig?.LogGroup, StringComparison.Ordinal))
{
request.LoggingConfig.LogGroup = logGroup;
different = true;
}
}

if (!different)
return null;

Expand Down
36 changes: 36 additions & 0 deletions src/Amazon.Lambda.Tools/LambdaDefinedCommandOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -486,5 +486,41 @@ public static class LambdaDefinedCommandOptions
ValueType = CommandOption.CommandOptionValueType.StringValue,
Description = $"Path to the directory to mount to the build container. Otherwise, look upward for a solution folder."
};
public static readonly CommandOption ARGUMENT_LOG_FORMAT =
new CommandOption
{
Name = "Log Format",
Switch = "--log-format",
ShortSwitch = "-lf",
ValueType = CommandOption.CommandOptionValueType.StringValue,
Description = $"The log format used by the Lambda function. Valid values are: Text or JSON. Default is Text"
};
public static readonly CommandOption ARGUMENT_LOG_APPLICATION_LEVEL =
new CommandOption
{
Name = "Application Log Level",
Switch = "--log-application-level",
ShortSwitch = "-lal",
ValueType = CommandOption.CommandOptionValueType.StringValue,
Description = $"The log level. Valid values are: TRACE, DEBUG, INFO, WARN, ERROR or FATAL. Default is INFO."
};
public static readonly CommandOption ARGUMENT_LOG_SYSTEM_LEVEL =
new CommandOption
{
Name = "System Log",
Switch = "--log-system-level",
ShortSwitch = "-lsl",
ValueType = CommandOption.CommandOptionValueType.StringValue,
Description = $"The log system level. Valid values are: DEBUG, INFO, WARN. Default is INFO."
};
public static readonly CommandOption ARGUMENT_LOG_GROUP =
new CommandOption
{
Name = "Log Group",
Switch = "--log-group",
ShortSwitch = "-lg",
ValueType = CommandOption.CommandOptionValueType.StringValue,
Description = $"The name of the Amazon CloudWatch log group the function sends logs to. Default is /aws/lambda/<function name>."
};
}
}
124 changes: 124 additions & 0 deletions test/Amazon.Lambda.Tools.Test/ApplySettingsTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Amazon.Lambda.Model;
using Amazon.Lambda.Tools.Commands;
using Moq;
using Xunit;
using Xunit.Abstractions;

namespace Amazon.Lambda.Tools.Test
{
public class ApplySettingsTest
{
private readonly ITestOutputHelper _testOutputHelper;

public ApplySettingsTest(ITestOutputHelper testOutputHelper)
{
this._testOutputHelper = testOutputHelper;
}

[Fact]
public async Task SetLoggingPropertiesForCreateRequest()
{
var mockClient = new Mock<IAmazonLambda>();

mockClient.Setup(client => client.CreateFunctionAsync(It.IsAny<CreateFunctionRequest>(), It.IsAny<CancellationToken>()))
.Callback<CreateFunctionRequest, CancellationToken>((request, token) =>
{
Assert.Equal("JSON", request.LoggingConfig.LogFormat);
Assert.Equal("TheGroup", request.LoggingConfig.LogGroup);
Assert.Equal("DEBUG", request.LoggingConfig.ApplicationLogLevel);
Assert.Equal("WARN", request.LoggingConfig.SystemLogLevel);
})
.Returns((CreateFunctionRequest r, CancellationToken token) =>
{
return Task.FromResult(new CreateFunctionResponse());
});

var assembly = this.GetType().GetTypeInfo().Assembly;

var fullPath = Path.GetFullPath(Path.GetDirectoryName(assembly.Location) + "../../../../../../testapps/TestFunction");
var command = new DeployFunctionCommand(new TestToolLogger(_testOutputHelper), fullPath, new string[0]);
command.FunctionName = "test-function-" + DateTime.Now.Ticks;
command.Handler = "TestFunction::TestFunction.Function::ToUpper";
command.Timeout = 10;
command.MemorySize = 512;
command.Role = await TestHelper.GetTestRoleArnAsync();
command.Configuration = "Release";
command.Runtime = "dotnet8";
command.LogFormat = "JSON";
command.LogGroup = "TheGroup";
command.LogApplicationLevel = "DEBUG";
command.LogSystemLevel = "WARN";
command.DisableInteractive = true;
command.LambdaClient = mockClient.Object;

var created = await command.ExecuteAsync();
Assert.True(created);
}

[Fact]
public async Task SetLoggingPropertiesForUpdateRequest()
{
var assembly = this.GetType().GetTypeInfo().Assembly;

var fullPath = Path.GetFullPath(Path.GetDirectoryName(assembly.Location) + "../../../../../../testapps/TestFunction");
var command = new DeployFunctionCommand(new TestToolLogger(_testOutputHelper), fullPath, new string[0]);
command.FunctionName = "test-function-" + DateTime.Now.Ticks;
command.Handler = "TestFunction::TestFunction.Function::ToUpper";
command.Timeout = 10;
command.MemorySize = 512;
command.Role = await TestHelper.GetTestRoleArnAsync();
command.Configuration = "Release";
command.Runtime = "dotnet8";
command.LogFormat = "JSON";
command.LogGroup = "TheGroup";
command.LogApplicationLevel = "DEBUG";
command.LogSystemLevel = "WARN";
command.DisableInteractive = true;

var mockClient = new Mock<IAmazonLambda>();

mockClient.Setup(client => client.GetFunctionConfigurationAsync(It.IsAny<GetFunctionConfigurationRequest>(), It.IsAny<CancellationToken>()))
.Returns((GetFunctionConfigurationRequest r, CancellationToken token) =>
{
var response = new GetFunctionConfigurationResponse
{
FunctionName = command.FunctionName,
Handler = command.Handler,
Timeout = command.Timeout.Value,
MemorySize = command.MemorySize.Value,
Role = command.Role,
Runtime = command.Runtime
};

return Task.FromResult(response);
});

mockClient.Setup(client => client.UpdateFunctionConfigurationAsync(It.IsAny<UpdateFunctionConfigurationRequest>(), It.IsAny<CancellationToken>()))
.Callback<UpdateFunctionConfigurationRequest, CancellationToken>((request, token) =>
{
Assert.Equal("JSON", request.LoggingConfig.LogFormat);
Assert.Equal("TheGroup", request.LoggingConfig.LogGroup);
Assert.Equal("DEBUG", request.LoggingConfig.ApplicationLogLevel);
Assert.Equal("WARN", request.LoggingConfig.SystemLogLevel);
})
.Returns((CreateFunctionRequest r, CancellationToken token) =>
{
return Task.FromResult(new UpdateFunctionConfigurationResponse());
});


command.LambdaClient = mockClient.Object;

var created = await command.ExecuteAsync();
Assert.True(created);
}
}
}

0 comments on commit 4a9bbc7

Please sign in to comment.