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

Add LoggerMessage.Define overload to disable IsEnabled check #45290

Closed
Tracked by #5929
davidfowl opened this issue Nov 27, 2020 · 8 comments · Fixed by #50334
Closed
Tracked by #5929

Add LoggerMessage.Define overload to disable IsEnabled check #45290

davidfowl opened this issue Nov 27, 2020 · 8 comments · Fixed by #50334
Assignees
Labels
api-approved API was approved in API review, it can be implemented area-Extensions-Logging
Milestone

Comments

@davidfowl
Copy link
Member

Background and Motivation

LoggerMessage.Define allows skipping various overheads in logging (parsing the template etc) and returns delegate that performs logging and includes a check if that logging category is enabled before performing the call to log. In some cases it's important that check be moved higher up the stack to avoid transformation of parameters before calling log, for e.g.

public class Connection
{
    private string _connectionId;
    public string ConnectionId => _connectionId ??= GenerateConnectionId();
}

public static class Log
{
    private static readonly Action<ILogger, string, Exception> _connectionStart =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(1, "ConnectionStarted"),
            "Connection {ConnectionId} started");

    public static void ConnectionStarted(ILogger logger, Connection connection)
    {
          _connectionStart(logger, connection.ConnectionId, null);
    }
}

The above logic ends up eagerly allocating the ConnectionId even when the log level isn't enabled. The code could be changed to this:

public static void ConnectionStarted(ILogger logger, Connection connection)
{
+    if (logger.IsEnabled(LogLevel.Debug))
+    {
        _connectionStart(logger, connection.ConnectionId, null);
+    }
}

It would end up checking if the category is enabled multiple times when the category is on.

Proposed API

namespace Microsoft.Extensions.Logging
{
    public static class LoggerMessage
    {
+        public static Action<ILogger, T1...T6, Exception> Define<T1...T6>(LogLevel logLevel, EventId eventId, string formatString, bool skipEnabledCheck);
    }

The ... are overloads T1 to T6 (or more).

Usage Examples

public class Connection
{
    private string _connectionId;
    public string ConnectionId => _connectionId ??= GenerateConnectionId();
}

public static class Log
{
    private static readonly Action<ILogger, string, Exception> _connectionStart =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(1, "ConnectionStarted"),
            "Connection {ConnectionId} started",
            skipEnabledCheck: true);

    public static void ConnectionStarted(ILogger logger, Connection connection)
    {
        if (logger.IsEnabled(LogLevel.Debug))
        {
            _connectionStart(logger, connection.ConnectionId, null);
        }
    }
}

Risks

None.

@davidfowl davidfowl added the api-suggestion Early API idea and discussion, it is NOT ready for implementation label Nov 27, 2020
@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added area-Extensions-Logging untriaged New issue has not been triaged by the area owner labels Nov 27, 2020
@ghost
Copy link

ghost commented Nov 27, 2020

Tagging subscribers to this area: @maryamariyan
See info in area-owners.md if you want to be subscribed.

Issue Details

Background and Motivation

LoggerMessage.Define allows skipping various overheads in logging (parsing the template etc) and returns delegate that performs logging and includes a check if that logging category is enabled before performing the call to log. In some cases it's important that check be moved higher up the stack to avoid transformation of parameters before calling log, for e.g.

public class Connection
{
    private string _connectionId;
    public string ConnectionId => _connectionId ??= GenerateConnectionId();
}

public static class Log
{
    private static readonly Action<ILogger, string, Exception> _connectionStart =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(1, "ConnectionStarted"),
            "Connection {ConnectionId} started");

    public static void ConnectionStarted(ILogger logger, Connection connection)
    {
          _connectionStart(logger, connection.ConnectionId, null);
    }
}

The above logic ends up eagerly allocating the ConnectionId even when the log level isn't enabled. The code could be changed to this:

public static void ConnectionStarted(ILogger logger, Connection connection)
{
+    if (logger.IsEnabled(LogLevel.Debug))
+    {
        _connectionStart(logger, connection.ConnectionId, null);
+    }
}

It would end up checking if the category is enabled multiple times when the category is on.

Proposed API

namespace Microsoft.Extensions.Logging
{
    public static class LoggerMessage
    {
+        public static Action<ILogger, T1...T6, Exception> Define<T1...T6>(LogLevel logLevel, EventId eventId, string formatString, bool skipEnabledCheck);
    }

The ... are overloads T1 to T6 (or more).

Usage Examples

public class Connection
{
    private string _connectionId;
    public string ConnectionId => _connectionId ??= GenerateConnectionId();
}

public static class Log
{
    private static readonly Action<ILogger, string, Exception> _connectionStart =
        LoggerMessage.Define<string>(
            LogLevel.Debug,
            new EventId(1, "ConnectionStarted"),
            "Connection {ConnectionId} started",
            skipEnabledCheck: true);

    public static void ConnectionStarted(ILogger logger, Connection connection)
    {
        if (logger.IsEnabled(LogLevel.Debug))
        {
            _connectionStart(logger, connection.ConnectionId, null);
        }
    }
}

Risks

None.

Author: davidfowl
Assignees: -
Labels:

api-suggestion, area-Extensions-Logging, untriaged

Milestone: -

@maryamariyan maryamariyan removed the untriaged New issue has not been triaged by the area owner label Dec 10, 2020
@maryamariyan maryamariyan added this to the 6.0.0 milestone Dec 10, 2020
@maryamariyan maryamariyan added api-ready-for-review API is ready for review, it is NOT ready for implementation and removed api-suggestion Early API idea and discussion, it is NOT ready for implementation labels Dec 10, 2020
@bartonjs
Copy link
Member

bartonjs commented Jan 15, 2021

Video

Looks good as proposed.

namespace Microsoft.Extensions.Logging
{
    public static class LoggerMessage
    {
        public static Action<ILogger, T1, Exception> Define<T1>(LogLevel logLevel, EventId eventId, string formatString);
        public static Action<ILogger, T1, T2, Exception> Define<T1, T2>(LogLevel logLevel, EventId eventId, string formatString);
        public static Action<ILogger, T1, T2, T3, Exception> Define<T1, T2, T3>(LogLevel logLevel, EventId eventId, string formatString);
        public static Action<ILogger, T1, T2, T3, T4, Exception> Define<T1, T2, T3, T4>(LogLevel logLevel, EventId eventId, string formatString);
        public static Action<ILogger, T1, T2, T3, T4, T5, Exception> Define<T1, T2, T3, T4, T5>(LogLevel logLevel, EventId eventId, string formatString);
        public static Action<ILogger, T1, T2, T3, T4, T5, T6, Exception> Define<T1, T2, T3, T4, T5, T6>(LogLevel logLevel, EventId eventId, string formatString);
    }
}

@bartonjs bartonjs added api-approved API was approved in API review, it can be implemented and removed api-ready-for-review API is ready for review, it is NOT ready for implementation labels Jan 15, 2021
@gfoidl
Copy link
Member

gfoidl commented Jan 16, 2021

@bartonjs did you copy the wrong overloads? These are already here:

public static Action<ILogger, T1, Exception?> Define<T1>(LogLevel logLevel, EventId eventId, string formatString)
(except the nullability on Exception) and miss the proposed argument skipEnabledCheck.

What about the overload that doesn't take any T1...T6?

public static Action<ILogger, Exception?> Define(LogLevel logLevel, EventId eventId, string formatString)


PS: I have a PR ready, just need to submit it when the new overloads are confirmed.

@KalleOlaviNiemitalo
Copy link

PS: I have a PR ready, just need to submit it when the new overloads are confirmed.

Renaming "master" to "main" in #48357 broke the link. https://github.com/dotnet/runtime/compare/main...gfoidl:loggermessage-skipEnabledCheck?expand=1 works instead.

You added skipEnabledCheck checks inside each lambda expression. This could cause a minor slowdown in every logging call but I don't know whether it will be measurable. If a benchmark shows that it is indeed slower, then an alternative would be to do the check outside the lambda expression:

            if (skipEnabledCheck)
            {
                return (logger, exception) => { /* just call logger.Log and not logger.IsEnabled */ };
            }
            else
            {
                return (logger, exception) => { /* call logger.IsEnabled and logger.Log */ };
            }

That would be more work for the JIT compiler, though.

@davidfowl
Copy link
Member Author

I would prefer if it returned a different lambda without the check. @KalleOlaviNiemitalo are you going to submite a PR?

@KalleOlaviNiemitalo
Copy link

No, I do not intend to make a PR.

@gfoidl
Copy link
Member

gfoidl commented Mar 28, 2021

But I can take the suggestion from @KalleOlaviNiemitalo and incorporate them into my changes.

@gfoidl
Copy link
Member

gfoidl commented Mar 28, 2021

Filed dotnet/aspnetcore#31332 so that these new API can be used for ASP.NET Core too.

@ghost ghost locked as resolved and limited conversation to collaborators Apr 28, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
api-approved API was approved in API review, it can be implemented area-Extensions-Logging
Projects
None yet
Development

Successfully merging a pull request may close this issue.

6 participants