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

improv: improve logger extra keys feature #244

Draft
wants to merge 4 commits into
base: develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 97 additions & 8 deletions docs/core/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ It accepts any dictionary, and all keyword arguments will be added as part of th

=== "Function.cs"

```c# hl_lines="16"
```c# hl_lines="14 17 21 29"
/**
* Handler for requests to Lambda function.
*/
Expand All @@ -323,16 +323,105 @@ It accepts any dictionary, and all keyword arguments will be added as part of th
{
var requestContextRequestId = apigwProxyEvent.RequestContext.RequestId;

var lookupId = new Dictionary<string, object>()
// Pass an object for logging additional data
var lookupInfo1 = new { LookupId = requestContextRequestId };
Logger.LogInformation(lookupInfo1, "This is a log with anonymous type object additional data");

// Specify the key name for extra additional data
Logger.LogInformation(("LookupInfo", lookupInfo1), "This is a log specifying the key name for additional data");

// Pass a typed object for logging additional data
var lookupInfo2 = new LookupInfo { LookupId = requestContextRequestId };
Logger.LogInformation(lookupInfo2, "This is a log with typed object additional data");

// Pass multiple extra key/value pairs
var extraKeys = new Dictionary<string, object>()
{
{ "LookupId", requestContextRequestId }
{ "LookupInfo", new { LookupId = requestContextRequestId } },
{ "CorrelationIds", new { MyCorrelationId = "correlation_id_value" } }
};
Logger.LogInformation(extraKeys, "This is a log with multiple extra key/value pairs");
...
}
public class LookupInfo
{
public string? LookupId { get; set; }
}
```

=== "Example CloudWatch Logs excerpt"

// Appended keys are added to all subsequent log entries in the current execution.
// Call this method as early as possible in the Lambda handler.
// Typically this is value would be passed into the function via the event.
// Set the ClearState = true to force the removal of keys across invocations,
Logger.AppendKeys(lookupId);
```json hl_lines="4-6 21-23 38-40 55-60"
{
"cold_start": true,
"xray_trace_id": "1-61b7add4-66532bb81441e1b060389429",
"extra": {
"lookup_id": "4c50eace-8b1e-43d3-92ba-0efacf5d1625"
},
"function_name": "test",
"function_version": "$LATEST",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"level": "Information",
"service": "lambda-example",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "This is a log with anonymous type object additional data",
}
{
"cold_start": true,
"xray_trace_id": "1-61b7add4-66532bb81441e1b060389429",
"lookup_info": {
"lookup_id": "4c50eace-8b1e-43d3-92ba-0efacf5d1625"
},
"function_name": "test",
"function_version": "$LATEST",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"level": "Information",
"service": "lambda-example",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "This is a log specifying the key name for additional data",
}
{
"cold_start": true,
"xray_trace_id": "1-61b7add4-66532bb81441e1b060389429",
"lookup_info": {
"lookup_id": "4c50eace-8b1e-43d3-92ba-0efacf5d1625"
},
"function_name": "test",
"function_version": "$LATEST",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"level": "Information",
"service": "lambda-example",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "This is a log with typed object additional data",
}
{
"cold_start": true,
"xray_trace_id": "1-61b7add4-66532bb81441e1b060389429",
"lookup_info": {
"lookup_id": "4c50eace-8b1e-43d3-92ba-0efacf5d1625"
},
"correlation_ids": {
"my_correlation_id": "correlation_id_value"
},
"function_name": "test",
"function_version": "$LATEST",
"function_memory_size": 128,
"function_arn": "arn:aws:lambda:eu-west-1:12345678910:function:test",
"function_request_id": "52fdfc07-2182-154f-163f-5f0f9a621d72",
"timestamp": "2021-12-13T20:32:22.5774262Z",
"level": "Information",
"service": "lambda-example",
"name": "AWS.Lambda.Powertools.Logging.Logger",
"message": "This is a log with multiple extra key/value pairs",
}
```

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,9 @@ internal static class LoggingConstants
/// Constant for key exception
/// </summary>
internal const string KeyException = "Exception";

/// <summary>
/// Constant for key extra
/// </summary>
internal const string KeyExtra = "Extra";
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.Encodings.Web;
using System.Text.Json;
using AWS.Lambda.Powertools.Common;
Expand Down Expand Up @@ -139,10 +140,10 @@ internal void EndScope()
private static Dictionary<string, object> GetScopeKeys<TState>(TState state)
{
var keys = new Dictionary<string, object>();
if (state is null)

if (state is null)
return keys;

switch (state)
{
case IEnumerable<KeyValuePair<string, string>> pairs:
Expand All @@ -152,6 +153,7 @@ internal void EndScope()
if (!string.IsNullOrWhiteSpace(key))
keys.TryAdd(key, value);
}

break;
}
case IEnumerable<KeyValuePair<string, object>> pairs:
Expand All @@ -161,21 +163,70 @@ internal void EndScope()
if (!string.IsNullOrWhiteSpace(key))
keys.TryAdd(key, value);
}

break;
}
case KeyValuePair<string, string>(var key, var value):
{
if (!string.IsNullOrWhiteSpace(key))
keys.TryAdd(key, value);
break;
}
case KeyValuePair<string, object>(var key, var value):
{
if (!string.IsNullOrWhiteSpace(key))
keys.TryAdd(key, value);
break;
}
case ITuple pair:
{
if (pair.Length == 2 && pair[0] is string key && !string.IsNullOrWhiteSpace(key))
keys.TryAdd(key, pair[1]);
else
keys.TryAdd(LoggingConstants.KeyExtra, state);
break;
}
default:
{
foreach (var property in state.GetType().GetProperties())
{
keys.TryAdd(property.Name, property.GetValue(state));
}
keys.TryAdd(GetScopeKey(state), state);
break;
}
}

return keys;
}

/// <summary>
/// Extract provided scope key
/// </summary>
/// <typeparam name="TState">The type of the t state.</typeparam>
/// <param name="state">The state.</param>
/// <returns>Key for the provided scope key</returns>
private static string GetScopeKey<TState>(TState state)
{
if (state is null or string)
return LoggingConstants.KeyExtra;

var type = state.GetType();
if (type.IsEnum ||
type.IsArray ||
type.IsPrimitive ||
string.IsNullOrWhiteSpace(type.Namespace))
return LoggingConstants.KeyExtra;

var typeName = type.Name;
if (type.IsGenericType)
typeName = type.Name.Split('`').First();

if (typeName.Contains('[') ||
typeName.Contains(']') ||
typeName.Contains('<') ||
typeName.Contains('>'))
return LoggingConstants.KeyExtra;

return typeName;
}

/// <summary>
/// Determines whether the specified log level is enabled.
/// </summary>
Expand Down
Loading