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

SLVS-1723 Introduce log contexts #5913

Merged
merged 18 commits into from
Jan 8, 2025

Conversation

georgii-borovinskikh-sonarsource
Copy link
Contributor

@georgii-borovinskikh-sonarsource georgii-borovinskikh-sonarsource commented Dec 19, 2024

Base automatically changed from feature/hardening-q4-6 to master December 20, 2024 10:16
@georgii-borovinskikh-sonarsource georgii-borovinskikh-sonarsource changed the base branch from master to feature/hardening-q4 December 23, 2024 09:07
@georgii-borovinskikh-sonarsource georgii-borovinskikh-sonarsource changed the base branch from feature/hardening-q4 to gb/collections-immutable-reference December 23, 2024 09:16
@georgii-borovinskikh-sonarsource georgii-borovinskikh-sonarsource marked this pull request as ready for review December 23, 2024 13:49
Base automatically changed from gb/collections-immutable-reference to feature/hardening-q4 December 24, 2024 09:44
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not really happy with the design decisions as we are creating a lot of instances of the logger and the context, which will increase the memory usage and the performance overhead especially because logging is a very used operation.

I would rather make the LoggerBase and LoggerContextManager Mef exportable components with the creation policy non-shared.
Then the contextManager of a logger can be configured to hold a specific context or to enhance existing contexts.

src/Roslyn.Suppressions/Roslyn.Suppressions/Logger.cs Outdated Show resolved Hide resolved
src/Integration.UnitTests/Helpers/SonarLintOutputTests.cs Outdated Show resolved Hide resolved
[Export(typeof(ILoggerFactory))]
[PartCreationPolicy(CreationPolicy.NonShared)]
[method: ImportingConstructor]
public class LoggerFactory(ILoggerContextManager loggerContextManager) : ILoggerFactory

Choose a reason for hiding this comment

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

The LoggerFactory is a level an abstraction that does not actually bring any value. It's just used in a test class and in the Container.cs.
Please remove it completely and all test classes related to it

Copy link
Contributor Author

Choose a reason for hiding this comment

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

LoggerContextManager implementation is not accessible to Container.cs (as it is an internal class). I think this is a better solution than making the class public. Also it means that users of LoggerBase don't need to mef-export the manager and it also allows to use Substitute (by returning ILogger)

src/Core.UnitTests/Logging/LoggerContextManagerTests.cs Outdated Show resolved Hide resolved
src/Core.UnitTests/Logging/LoggerContextManagerTests.cs Outdated Show resolved Hide resolved
}

private static StringBuilder AppendProperty(StringBuilder builder, string property) =>
builder.Append('[').Append(property).Append(']').Append(' ');

Choose a reason for hiding this comment

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

I think this is a lot more readable

Suggested change
builder.Append('[').Append(property).Append(']').Append(' ');
builder.AppendFormat("[{0}] ", property);

builder.Append('[').Append(property).Append(']').Append(' ');

private static StringBuilder AppendPropertyFormat(StringBuilder builder, string property, params object[] args) =>
builder.Append('[').AppendFormat(property, args).Append(']').Append(' ');

Choose a reason for hiding this comment

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

A lot more readable

Suggested change
builder.Append('[').AppendFormat(property, args).Append(']').Append(' ');
builder.AppendFormat("[{0}] ", new StringBuilder().AppendFormat(property, args));

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Technically this is faster and has less allocations

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Chars about 2x faster than double formatting and uses less memory. I don't think it's that unreadable.

private static StringBuilder AppendProperty(StringBuilder builder, string property) =>
builder.Append('[').Append(property).Append(']').Append(' ');

private static StringBuilder AppendPropertyFormat(StringBuilder builder, string property, params object[] args) =>

Choose a reason for hiding this comment

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

Please make the return type for both AppendProperty void. Or create extension methods for this class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

What's the motivation for that?

Choose a reason for hiding this comment

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

The return type of AppendPropertyFormat is not used. Additionally, it feels awkward to receive a stringBuilder instance then return it unchanged.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Created extension methods

ILoggerWriter writer,
ILoggerSettingsProvider settingsProvider) : ILogger
{
public ILogger ForContext(params string[] context) =>

Choose a reason for hiding this comment

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

Creating a new instance for each context feels like a very bad design decision as this will increase the memory usage and the performance overhead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is not a correct conclusion. The instances are lightweight (partially thanks to immutable lists and lazy string property initialization, but in general they don't hold a lot of data apart from a few object references and the aforementioned property list and formatted string). This will not increase the overhead in any meaningful way.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

On top of that, most of these instances will be created in user-class constructors and therefore will be long-living.

Copy link
Contributor

Choose a reason for hiding this comment

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

It is a correct conclusion and it will bring more pressure to the garbage collector, which will impact the performance.

On top of that, most of these instances will be created in user-class constructors and therefore will be long-living.

This is correct for the LoggerBase instances. That's why I see no reason why it shouldn't be a Mef component with a non-shared creation policy (which will basically lead to the same thing but without the need of factories).
This is not true, however for the LoggerContextManager and I think we can find better design than just creating instances for each context.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Again, that's not true. It will, of course impact the performance, as pretty much every single added allocation or instruction does, but the question is will it impact it in any meaningful way? The answer is no. It has less of an impact on memory than the existing string allocations inside the logger.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Uncustomized logger context manager has references to 2 empty immutable lists and 2 non-initialized lazies...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added log methods overloads for single-time-use contexts

public string FormatedContext => formatedContext.Value;
public string FormatedVerboseContext => formatedVerboseContext.Value;

public ILoggerContextManager CreateAugmentedContext(IEnumerable<string> additionalContexts) => new LoggerContextManager(contexts.AddRange(additionalContexts), verboseContexts);

Choose a reason for hiding this comment

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

Creating a new instance for each context feels like a very bad design decision as this will increase the memory usage and the performance overhead.

Copy link
Contributor

Choose a reason for hiding this comment

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

I still have some suggestions, but the implementation seems better now

}
internal static class StringBuilderLoggingExtensions
{
public static StringBuilder AppendProperty(this StringBuilder builder, string property) =>

Choose a reason for hiding this comment

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

I would rename it

Suggested change
public static StringBuilder AppendProperty(this StringBuilder builder, string property) =>
public static StringBuilder AppendContextProperty(this StringBuilder builder, string property) =>

WriteLine(messageFormat, args);
}
}
public static StringBuilder AppendPropertyFormat(this StringBuilder builder, string property, params object[] args) =>

Choose a reason for hiding this comment

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

I would rename it

Suggested change
public static StringBuilder AppendPropertyFormat(this StringBuilder builder, string property, params object[] args) =>
public static StringBuilder AppendContextPropertyFormat(this StringBuilder builder, string property, params object[] args) =>

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Log properties also include thread id, debug, possibly other things. Context is a word I used to specifically describe the log properties that are set for a certain logical scope

src/Core.UnitTests/Logging/LoggerContextManagerTests.cs Outdated Show resolved Hide resolved
src/Core.UnitTests/Logging/LoggerContextManagerTests.cs Outdated Show resolved Hide resolved
src/Core.UnitTests/Logging/LoggerBaseTests.cs Outdated Show resolved Hide resolved
src/Core.UnitTests/Logging/LoggerBaseTests.cs Outdated Show resolved Hide resolved
/// <summary>
/// Logs a message and appends a new line.
/// </summary>
void WriteLine(string message);

Choose a reason for hiding this comment

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

Let's get rid of this overload to have less clutter in the interface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

technically this overload is faster than the format overload, because string/sb format checks for {0} in the string even if the parameters list is empty

writer,
settingsProvider);

public void WriteLine(string message) =>

Choose a reason for hiding this comment

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

We could actually get rid of this overload.

void LogVerbose(string messageFormat, params object[] args);
void LogVerbose(MessageLevelContext context, string messageFormat, params object[] args);

ILogger ForContext(params string[] context);

Choose a reason for hiding this comment

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

Consider renaming these two methods (lines 38 and 40) with something like GetNewWithContext, CreateNewWithContext.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think it's enough that it has a return type of ILogger, this implies that it's a new instance

… responsibility from LoggerBase to LoggerContextManager, added StringBuilderLoggingExtensionsTests
@georgii-borovinskikh-sonarsource georgii-borovinskikh-sonarsource merged commit 9051497 into feature/hardening-q4 Jan 8, 2025
4 checks passed
@georgii-borovinskikh-sonarsource georgii-borovinskikh-sonarsource deleted the gb/log-context branch January 8, 2025 13:01
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.

2 participants