Skip to content

Commit 969aa52

Browse files
authored
Add support configuring logging for Lambda functions
1 parent 80c2ace commit 969aa52

File tree

6 files changed

+269
-3
lines changed

6 files changed

+269
-3
lines changed

src/Amazon.Lambda.Tools/Amazon.Lambda.Tools.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
<Copyright>Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.</Copyright>
1616
<Product>AWS Lambda Tools for .NET CLI</Product>
1717
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
18-
<Version>5.10.7</Version>
18+
<Version>5.11.0</Version>
1919
</PropertyGroup>
2020
<ItemGroup>
2121
<Content Include="Resources\build-lambda-zip.exe">

src/Amazon.Lambda.Tools/Commands/DeployFunctionCommand.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,12 @@ public class DeployFunctionCommand : UpdateFunctionConfigCommand
7979

8080
LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD,
8181
LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD,
82-
LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY
82+
LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY,
83+
84+
LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT,
85+
LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL,
86+
LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL,
87+
LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP,
8388
});
8489

8590
public string Architecture { get; set; }
@@ -319,6 +324,13 @@ protected override async Task<bool> PerformActionAsync()
319324
{
320325
SubnetIds = this.GetStringValuesOrDefault(this.SubnetIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SUBNETS, false)?.ToList(),
321326
SecurityGroupIds = this.GetStringValuesOrDefault(this.SecurityGroupIds, LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_SECURITY_GROUPS, false)?.ToList()
327+
},
328+
LoggingConfig = new LoggingConfig
329+
{
330+
LogFormat = this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false),
331+
ApplicationLogLevel = this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false),
332+
SystemLogLevel = this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL, false),
333+
LogGroup = this.GetStringValueOrDefault(this.LogGroup, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false),
322334
}
323335
};
324336

@@ -622,6 +634,11 @@ protected override void SaveConfigFile(JsonData data)
622634
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD.ConfigFileKey, this.GetBoolValueOrDefault(this.UseContainerForBuild, LambdaDefinedCommandOptions.ARGUMENT_USE_CONTAINER_FOR_BUILD, false));
623635
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD.ConfigFileKey, this.GetStringValueOrDefault(this.ContainerImageForBuild, LambdaDefinedCommandOptions.ARGUMENT_CONTAINER_IMAGE_FOR_BUILD, false));
624636
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY.ConfigFileKey, this.GetStringValueOrDefault(this.CodeMountDirectory, LambdaDefinedCommandOptions.ARGUMENT_CODE_MOUNT_DIRECTORY, false));
637+
638+
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT.ConfigFileKey, this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false));
639+
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL.ConfigFileKey, this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false));
640+
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL.ConfigFileKey, this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL, false));
641+
data.SetIfNotNull(LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP.ConfigFileKey, this.GetStringValueOrDefault(this.LogGroup, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false));
625642
}
626643
}
627644
}

src/Amazon.Lambda.Tools/Commands/GetFunctionConfigCommand.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,15 @@ protected override async Task<bool> PerformActionAsync()
146146
this.Logger.WriteLine(" Auth: ".PadRight(PAD_SIZE) + urlConfig.AuthType.Value);
147147
}
148148

149+
if (response.LoggingConfig != null)
150+
{
151+
this.Logger.WriteLine("Logging Config");
152+
this.Logger.WriteLine(" Format: ".PadRight(PAD_SIZE) + response.LoggingConfig.LogFormat);
153+
this.Logger.WriteLine(" Application Log Level: ".PadRight(PAD_SIZE) + response.LoggingConfig.ApplicationLogLevel);
154+
this.Logger.WriteLine(" System Log Level: ".PadRight(PAD_SIZE) + response.LoggingConfig.SystemLogLevel);
155+
this.Logger.WriteLine(" Log Group: ".PadRight(PAD_SIZE) + response.LoggingConfig.LogGroup);
156+
}
157+
149158
return true;
150159
}
151160

src/Amazon.Lambda.Tools/Commands/UpdateFunctionConfigCommand.cs

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ public class UpdateFunctionConfigCommand : LambdaBaseCommand
5454
LambdaDefinedCommandOptions.ARGUMENT_ENVIRONMENT_VARIABLES,
5555
LambdaDefinedCommandOptions.ARGUMENT_APPEND_ENVIRONMENT_VARIABLES,
5656
LambdaDefinedCommandOptions.ARGUMENT_KMS_KEY_ARN,
57-
LambdaDefinedCommandOptions.ARGUMENT_APPLY_DEFAULTS_FOR_UPDATE_OBSOLETE
57+
LambdaDefinedCommandOptions.ARGUMENT_APPLY_DEFAULTS_FOR_UPDATE_OBSOLETE,
58+
59+
LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT,
60+
LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL,
61+
LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL,
62+
LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP,
5863
});
5964

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

8994
public string FunctionUrlLink { get; private set; }
9095

96+
public string LogFormat { get; set; }
97+
public string LogApplicationLevel { get; set; }
98+
public string LogSystemLevel { get; set; }
99+
public string LogGroup { get; set; }
100+
101+
91102
public UpdateFunctionConfigCommand(IToolLogger logger, string workingDirectory, string[] args)
92103
: base(logger, workingDirectory, UpdateCommandOptions, args)
93104
{
@@ -161,6 +172,15 @@ protected override void ParseCommandArguments(CommandOptions values)
161172
this.FunctionUrlEnable = tuple.Item2.BoolValue;
162173
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_FUNCTION_URL_AUTH.Switch)) != null)
163174
this.FunctionUrlAuthType = tuple.Item2.StringValue;
175+
176+
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT.Switch)) != null)
177+
this.LogFormat = tuple.Item2.StringValue;
178+
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL.Switch)) != null)
179+
this.LogApplicationLevel = tuple.Item2.StringValue;
180+
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL.Switch)) != null)
181+
this.LogSystemLevel = tuple.Item2.StringValue;
182+
if ((tuple = values.FindCommandOption(LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP.Switch)) != null)
183+
this.LogGroup = tuple.Item2.StringValue;
164184
}
165185

166186

@@ -623,6 +643,66 @@ private UpdateFunctionConfigurationRequest CreateConfigurationRequestIfDifferent
623643
}
624644
}
625645

646+
var logFormat = this.GetStringValueOrDefault(this.LogFormat, LambdaDefinedCommandOptions.ARGUMENT_LOG_FORMAT, false);
647+
if (!string.IsNullOrEmpty(logFormat))
648+
{
649+
if (request.LoggingConfig == null)
650+
{
651+
request.LoggingConfig = new LoggingConfig();
652+
}
653+
654+
if (!string.Equals(request.LoggingConfig.LogFormat, existingConfiguration.LoggingConfig?.LogFormat, StringComparison.Ordinal))
655+
{
656+
request.LoggingConfig.LogFormat = logFormat;
657+
different = true;
658+
}
659+
}
660+
661+
var logApplicationLevel = this.GetStringValueOrDefault(this.LogApplicationLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_APPLICATION_LEVEL, false);
662+
if (!string.IsNullOrEmpty(logApplicationLevel))
663+
{
664+
if (request.LoggingConfig == null)
665+
{
666+
request.LoggingConfig = new LoggingConfig();
667+
}
668+
669+
if (!string.Equals(request.LoggingConfig.ApplicationLogLevel, existingConfiguration.LoggingConfig?.ApplicationLogLevel, StringComparison.Ordinal))
670+
{
671+
request.LoggingConfig.ApplicationLogLevel = logApplicationLevel;
672+
different = true;
673+
}
674+
}
675+
676+
var logSystemLevel = this.GetStringValueOrDefault(this.LogSystemLevel, LambdaDefinedCommandOptions.ARGUMENT_LOG_SYSTEM_LEVEL, false);
677+
if (!string.IsNullOrEmpty(logSystemLevel))
678+
{
679+
if (request.LoggingConfig == null)
680+
{
681+
request.LoggingConfig = new LoggingConfig();
682+
}
683+
684+
if (!string.Equals(request.LoggingConfig.SystemLogLevel, existingConfiguration.LoggingConfig?.SystemLogLevel, StringComparison.Ordinal))
685+
{
686+
request.LoggingConfig.SystemLogLevel = logSystemLevel;
687+
different = true;
688+
}
689+
}
690+
691+
var logGroup = this.GetStringValueOrDefault(this.LogGroup, LambdaDefinedCommandOptions.ARGUMENT_LOG_GROUP, false);
692+
if (logGroup != null) // Allow empty string to reset back to Lambda's default log group.
693+
{
694+
if (request.LoggingConfig == null)
695+
{
696+
request.LoggingConfig = new LoggingConfig();
697+
}
698+
699+
if (!string.Equals(request.LoggingConfig.LogGroup, existingConfiguration.LoggingConfig?.LogGroup, StringComparison.Ordinal))
700+
{
701+
request.LoggingConfig.LogGroup = logGroup;
702+
different = true;
703+
}
704+
}
705+
626706
if (!different)
627707
return null;
628708

src/Amazon.Lambda.Tools/LambdaDefinedCommandOptions.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,5 +486,41 @@ public static class LambdaDefinedCommandOptions
486486
ValueType = CommandOption.CommandOptionValueType.StringValue,
487487
Description = $"Path to the directory to mount to the build container. Otherwise, look upward for a solution folder."
488488
};
489+
public static readonly CommandOption ARGUMENT_LOG_FORMAT =
490+
new CommandOption
491+
{
492+
Name = "Log Format",
493+
Switch = "--log-format",
494+
ShortSwitch = "-lf",
495+
ValueType = CommandOption.CommandOptionValueType.StringValue,
496+
Description = $"The log format used by the Lambda function. Valid values are: Text or JSON. Default is Text"
497+
};
498+
public static readonly CommandOption ARGUMENT_LOG_APPLICATION_LEVEL =
499+
new CommandOption
500+
{
501+
Name = "Application Log Level",
502+
Switch = "--log-application-level",
503+
ShortSwitch = "-lal",
504+
ValueType = CommandOption.CommandOptionValueType.StringValue,
505+
Description = $"The log level. Valid values are: TRACE, DEBUG, INFO, WARN, ERROR or FATAL. Default is INFO."
506+
};
507+
public static readonly CommandOption ARGUMENT_LOG_SYSTEM_LEVEL =
508+
new CommandOption
509+
{
510+
Name = "System Log",
511+
Switch = "--log-system-level",
512+
ShortSwitch = "-lsl",
513+
ValueType = CommandOption.CommandOptionValueType.StringValue,
514+
Description = $"The log system level. Valid values are: DEBUG, INFO, WARN. Default is INFO."
515+
};
516+
public static readonly CommandOption ARGUMENT_LOG_GROUP =
517+
new CommandOption
518+
{
519+
Name = "Log Group",
520+
Switch = "--log-group",
521+
ShortSwitch = "-lg",
522+
ValueType = CommandOption.CommandOptionValueType.StringValue,
523+
Description = $"The name of the Amazon CloudWatch log group the function sends logs to. Default is /aws/lambda/<function name>."
524+
};
489525
}
490526
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.IO;
4+
using System.Linq;
5+
using System.Reflection;
6+
using System.Text;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Amazon.Lambda.Model;
10+
using Amazon.Lambda.Tools.Commands;
11+
using Moq;
12+
using Xunit;
13+
using Xunit.Abstractions;
14+
15+
namespace Amazon.Lambda.Tools.Test
16+
{
17+
public class ApplySettingsTest
18+
{
19+
private readonly ITestOutputHelper _testOutputHelper;
20+
21+
public ApplySettingsTest(ITestOutputHelper testOutputHelper)
22+
{
23+
this._testOutputHelper = testOutputHelper;
24+
}
25+
26+
[Fact]
27+
public async Task SetLoggingPropertiesForCreateRequest()
28+
{
29+
var mockClient = new Mock<IAmazonLambda>();
30+
31+
mockClient.Setup(client => client.CreateFunctionAsync(It.IsAny<CreateFunctionRequest>(), It.IsAny<CancellationToken>()))
32+
.Callback<CreateFunctionRequest, CancellationToken>((request, token) =>
33+
{
34+
Assert.Equal("JSON", request.LoggingConfig.LogFormat);
35+
Assert.Equal("TheGroup", request.LoggingConfig.LogGroup);
36+
Assert.Equal("DEBUG", request.LoggingConfig.ApplicationLogLevel);
37+
Assert.Equal("WARN", request.LoggingConfig.SystemLogLevel);
38+
})
39+
.Returns((CreateFunctionRequest r, CancellationToken token) =>
40+
{
41+
return Task.FromResult(new CreateFunctionResponse());
42+
});
43+
44+
var assembly = this.GetType().GetTypeInfo().Assembly;
45+
46+
var fullPath = Path.GetFullPath(Path.GetDirectoryName(assembly.Location) + "../../../../../../testapps/TestFunction");
47+
var command = new DeployFunctionCommand(new TestToolLogger(_testOutputHelper), fullPath, new string[0]);
48+
command.FunctionName = "test-function-" + DateTime.Now.Ticks;
49+
command.Handler = "TestFunction::TestFunction.Function::ToUpper";
50+
command.Timeout = 10;
51+
command.MemorySize = 512;
52+
command.Role = await TestHelper.GetTestRoleArnAsync();
53+
command.Configuration = "Release";
54+
command.Runtime = "dotnet8";
55+
command.LogFormat = "JSON";
56+
command.LogGroup = "TheGroup";
57+
command.LogApplicationLevel = "DEBUG";
58+
command.LogSystemLevel = "WARN";
59+
command.DisableInteractive = true;
60+
command.LambdaClient = mockClient.Object;
61+
62+
var created = await command.ExecuteAsync();
63+
Assert.True(created);
64+
}
65+
66+
[Fact]
67+
public async Task SetLoggingPropertiesForUpdateRequest()
68+
{
69+
var assembly = this.GetType().GetTypeInfo().Assembly;
70+
71+
var fullPath = Path.GetFullPath(Path.GetDirectoryName(assembly.Location) + "../../../../../../testapps/TestFunction");
72+
var command = new DeployFunctionCommand(new TestToolLogger(_testOutputHelper), fullPath, new string[0]);
73+
command.FunctionName = "test-function-" + DateTime.Now.Ticks;
74+
command.Handler = "TestFunction::TestFunction.Function::ToUpper";
75+
command.Timeout = 10;
76+
command.MemorySize = 512;
77+
command.Role = await TestHelper.GetTestRoleArnAsync();
78+
command.Configuration = "Release";
79+
command.Runtime = "dotnet8";
80+
command.LogFormat = "JSON";
81+
command.LogGroup = "TheGroup";
82+
command.LogApplicationLevel = "DEBUG";
83+
command.LogSystemLevel = "WARN";
84+
command.DisableInteractive = true;
85+
86+
var mockClient = new Mock<IAmazonLambda>();
87+
88+
mockClient.Setup(client => client.GetFunctionConfigurationAsync(It.IsAny<GetFunctionConfigurationRequest>(), It.IsAny<CancellationToken>()))
89+
.Returns((GetFunctionConfigurationRequest r, CancellationToken token) =>
90+
{
91+
var response = new GetFunctionConfigurationResponse
92+
{
93+
FunctionName = command.FunctionName,
94+
Handler = command.Handler,
95+
Timeout = command.Timeout.Value,
96+
MemorySize = command.MemorySize.Value,
97+
Role = command.Role,
98+
Runtime = command.Runtime
99+
};
100+
101+
return Task.FromResult(response);
102+
});
103+
104+
mockClient.Setup(client => client.UpdateFunctionConfigurationAsync(It.IsAny<UpdateFunctionConfigurationRequest>(), It.IsAny<CancellationToken>()))
105+
.Callback<UpdateFunctionConfigurationRequest, CancellationToken>((request, token) =>
106+
{
107+
Assert.Equal("JSON", request.LoggingConfig.LogFormat);
108+
Assert.Equal("TheGroup", request.LoggingConfig.LogGroup);
109+
Assert.Equal("DEBUG", request.LoggingConfig.ApplicationLogLevel);
110+
Assert.Equal("WARN", request.LoggingConfig.SystemLogLevel);
111+
})
112+
.Returns((CreateFunctionRequest r, CancellationToken token) =>
113+
{
114+
return Task.FromResult(new UpdateFunctionConfigurationResponse());
115+
});
116+
117+
118+
command.LambdaClient = mockClient.Object;
119+
120+
var created = await command.ExecuteAsync();
121+
Assert.True(created);
122+
}
123+
}
124+
}

0 commit comments

Comments
 (0)