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

Initial PR for structured logging support #1588

Merged
merged 3 commits into from
Apr 4, 2024

Conversation

normj
Copy link
Member

@normj normj commented Sep 25, 2023

Description of changes:

This PR is the initial work to add support for structured logging natively in the Amazon.Lambda.RuntimeSupport the .NET Lambda runtime client (RIC). Serilog was used as the example of the experience but is not used directly so that we would not take a hard dependency on Serilog or interfere with anybody wanting to implement their own structured logging using Serilog.

Changes made:

  • The ILambdaLogger has new parameterized logging methods
  • The ConsoleLoggerWriter was refactored to have formatting being done in separate formatting classes that are swapped out depending on the environment variable configuring formatter. The DefaultLogMessageFormatter formatter represents what we do today and JsonLogMessageFormatter is the new structured logging.

NOTE: The new parameterize logging methods on ILambadLogger are marked with the .NET attribute RequiresPreviewFeatures. This requires users that want to use these methods to have to turn on preview language features in the project file, <LangVersion>preview</LangVersion>. This is done due to the fact that Amazon.Lambda.Core with these new APIs will go out before the .NET managed runtime is deployed with the version of Amazon.Lambda.RuntimeSupport. If users call these methods before the runtime is updated the message will be logged ignoring the parameters. For users that deploy as an executable with the latest version version of Amazon.Lambda.RuntimeSupport they will be able to use these methods with no problems. Once Amazon.Lambda.RuntimeSupport has been updated in the managed runtime a new version of Amazon.Lambda.Core will be released removing the RequiresPreviewFeatures attribute.

This PR uses a lot of the code ideas from PR #1323 by @dhhoang. The major difference are

  • Pushing more of the work into Amazon.Lambda.RuntimeSupport instead Amazon.Lambda.Core
  • Add more scenarios supported by Serilog
  • Match work Lambda team is doing in other language runtimes.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

…ent new JSON structured log formatter in Amazon.Lambda.RuntimeSupport
{
_writer.WriteLine(message);
_writer.WriteLine(exception.ToString());

Choose a reason for hiding this comment

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

null check needed ?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@@ -107,7 +128,7 @@ public class LogLevelLoggerWriter : IConsoleLoggerWriter
/// Amazon.Lambda.Core can not be relied on because the Lambda Function could be using
/// an older version of Amazon.Lambda.Core before LogLevel existed in Amazon.Lambda.Core.
/// </summary>
enum LogLevel
public enum LogLevel
Copy link

@birojnayak birojnayak Oct 6, 2023

Choose a reason for hiding this comment

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

are we making it public for user to use ? if it's internal to package, can we live with internal ?

Copy link
Member Author

Choose a reason for hiding this comment

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

This doesn't need to be public. Users interact with the LogLevel enum from Amazon.Lambda.Core. RuntimeSupport has to have a mirror of this enum because it can't rely on having the latest copy of Amazon.Lambda.Core when running that would have the enum.

using System.Globalization;
using System.Text;

namespace Amazon.Lambda.RuntimeSupport.Helpers.Logging
Copy link

@birojnayak birojnayak Oct 6, 2023

Choose a reason for hiding this comment

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

Here are few pointers as I was scanning through the Microsoft implementations. I am suggesting to see if we can get some inspirations and the code I am referring is well tested and inside their SDK (less buggy, so my inspirations :)).

  1. https://source.dot.net/#Microsoft.Extensions.Logging.Abstractions/LogValuesFormatter.cs,66cbe2d145df9bc8 => This is where given a string (in our case it's a message template), it spits out various params.
  2. https://source.dot.net/#Microsoft.Extensions.Logging.Abstractions/FormattedLogValues.cs,b8a158d8a121f959 => it holds on to the State (in our case MessageState). The ToString method uses the formatter to generate the string
  3. One of the Implementation I was looking for both Json and normal logging is EventSource => https://source.dot.net/#Microsoft.Extensions.Logging.EventSource/EventSourceLogger.cs,92 , which actually adds the json values for all properties as mentioned in the req/design spec.

/// <param name="message">Message to log.</param>
/// <param name="args">Values to be replaced in log messages that are parameterized.</param>
[RequiresPreviewFeatures(ParameterizedPreviewMessage)]
void Log(string level, string message, params object[] args) => Log(level, message);
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't args be passed down to the next function?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, good catch.

/// <param name="message">Message to log.</param>
/// <param name="args">Values to be replaced in log messages that are parameterized.</param>
[RequiresPreviewFeatures("Parameterized logging is in preview till new version of .NET Lambda runtime client is deployed to the .NET Lambda managed runtime.")]
void Log(string level, Exception exception, string message, params object[] args)
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment here. Not sure why args is not being used.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Comment on lines 59 to 62
/// <param name="level"></param>
/// <param name="exception"></param>
/// <param name="message"></param>
/// <param name="args"></param>
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you add more docs for the params similar to ILambdaLogger?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 34 to 35
/// <param name="messageTemplate"></param>
/// <returns></returns>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add docs for these tags

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

Comment on lines 113 to 114
/// <param name="state"></param>
/// <returns></returns>
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add docs for the empty tags in this class

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

private static readonly char[] PARAM_FORMAT_DELIMITERS = { ':' };

/// <summary>
/// Parse the string string representation of the message property without the brackets
Copy link
Contributor

Choose a reason for hiding this comment

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

string is duplicated

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

/// </summary>
public string MessageToken { get; private set; }

public enum Directive { Default, JsonSerialization };
Copy link
Contributor

Choose a reason for hiding this comment

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

add docs for this property

Copy link
Member Author

Choose a reason for hiding this comment

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

Done

@normj normj force-pushed the normj/initial-structured-logging branch from 56ba04d to 924b1cd Compare April 2, 2024 06:24
"project file to \"true\"";

/// <summary>
/// Log message catagorized by the given log level
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo in "catagorized"

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

void Log(string level, string message, params object[] args) => Log(level, message, args);

/// <summary>
/// Log message catagorized by the given log level
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo in "catagorized"

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

}

/// <summary>
/// Log message catagorized by the given log level
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo in "catagorized"

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

void Log(LogLevel level, string message, params object[] args) => Log(level.ToString(), message, args);

/// <summary>
/// Log message catagorized by the given log level
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo in "catagorized"

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

@@ -89,9 +104,18 @@ public void FormattedWriteLine(string message)
_writer.WriteLine(message);
}

public void FormattedWriteLine(string level, string message)
public void FormattedWriteLine(string level, string message, params object[] args)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add docs to public methods

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

_wrappedStdOutWriter.FormattedWriteLine(level, (Exception)null, message, args);
}

public void FormattedWriteLine(string level, Exception exception, string message, params object[] args)
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: add docs to public methods

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

}
else
{
argument = messageArguments[propertyIndex];
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you please add a null check for messageArguments before indexing into it? I got a warning for it in Rider.

Copy link
Member Author

Choose a reason for hiding this comment

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

I added a short circuit at the top that exits out if messageArguments is null or empty.

private static readonly UTF8Encoding UTF8NoBomNoThrow = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: false);

// Options used when serializing any message property values as a JSON to be added to the structured log message.
private JsonSerializerOptions _jsonSerialiazationOptions;
Copy link
Contributor

Choose a reason for hiding this comment

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

typo in _jsonSerialiazationOptions. should be _jsonSerializationOptions

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

writer.WriteStartObject();
foreach (DictionaryEntry entry in ((IDictionary)messageArgument))
{
writer.WritePropertyName(entry.Key.ToString());
Copy link
Contributor

Choose a reason for hiding this comment

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

entry.Key.ToString() throws a possible null warning. Could you change it to entry.Key.ToString() ?? string.Empty or something similar?

Copy link
Member Author

Choose a reason for hiding this comment

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

Done but if key is null by this point something has really gone wrong.


try
{
var formatedValue = string.Format("{0:" + formatArgument + "}", value);
Copy link
Contributor

Choose a reason for hiding this comment

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

typo in formatedValue

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed

Copy link
Member

@ashovlin ashovlin left a comment

Choose a reason for hiding this comment

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

Good assuming you address @philasmar 's comments, but don't need to see again.

@normj normj merged commit 920f42f into feature/structured-logging Apr 4, 2024
2 of 3 checks passed
@normj normj deleted the normj/initial-structured-logging branch April 4, 2024 22:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants