2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

.NET Core 汎用ホスト(GenericHost)ILoggerのログ出力メソッド

Last updated at Posted at 2020-02-01

このドキュメントの内容

汎用ホスト(GenericHost)におけるログ出力で使用する ILogger のログ出力メソッドの拡張について考えてみます。

このドキュメントの内容は .NET Core 3.1 で確認しています。

標準のログ出力メソッド

Microsoft.Extensions.Logging.ILogger インターフェースに定義されているログ出力メソッドは Log<TState> のみです。最終的にはこのメソッドに集約されることになります。

Microsoft.Extensions.Logging.ILogger
public interface ILogger
{
    IDisposable BeginScope<TState>(TState state);
    bool IsEnabled(LogLevel logLevel);
    void Log<TState>(
        LogLevel logLevel
        , EventId eventId
        , TState state
        , Exception exception
        , Func<TState, Exception, string> formatter
    );
}

Microsoft.Extensions.Logging.LoggerExtensions クラスに ILogger インターフェースに対する拡張メソッドが定義されています。通常はこれらのログ出力メソッドを使用することが多いと思います。

Microsoft.Extensions.Logging.LoggerExtensions
public static class LoggerExtensions
{
    public static IDisposable BeginScope(this ILogger logger, string messageFormat, params object[] args);

    public static void Log(this ILogger logger
        , LogLevel logLevel, Exception exception, string message, params object[] args
    );
    public static void Log(this ILogger logger
        , LogLevel logLevel, EventId eventId, string message, params object[] args
    );
    public static void Log(this ILogger logger
        , LogLevel logLevel, string message, params object[] args
    );
    public static void Log(this ILogger logger
        , LogLevel logLevel, EventId eventId, Exception exception, string message, params object[] args
    );

    public static void LogCritical(this ILogger logger
        , string message, params object[] args
    );
    public static void LogCritical(this ILogger logger
        , Exception exception, string message, params object[] args
    );
    public static void LogCritical(this ILogger logger
        , EventId eventId, string message, params object[] args
    );
    public static void LogCritical(this ILogger logger
        , EventId eventId, Exception exception, string message, params object[] args
    );

    public static void LogDebug(this ILogger logger
        , EventId eventId, Exception exception, string message, params object[] args
    );
    public static void LogDebug(this ILogger logger
        , EventId eventId, string message, params object[] args
    );
    public static void LogDebug(this ILogger logger
        , Exception exception, string message, params object[] args
    );
    public static void LogDebug(this ILogger logger
        , string message, params object[] args
    );

    public static void LogError(this ILogger logger
        , string message, params object[] args
    );
    public static void LogError(this ILogger logger
        , Exception exception, string message, params object[] args
    );
    public static void LogError(this ILogger logger
        , EventId eventId, Exception exception, string message, params object[] args
    );
    public static void LogError(this ILogger logger
        , EventId eventId, string message, params object[] args
    );

    public static void LogInformation(this ILogger logger
        , EventId eventId, string message, params object[] args
    );
    public static void LogInformation(this ILogger logger
        , Exception exception, string message, params object[] args
    );
    public static void LogInformation(this ILogger logger
        , EventId eventId, Exception exception, string message, params object[] args
    );
    public static void LogInformation(this ILogger logger
        , string message, params object[] args
    );

    public static void LogTrace(this ILogger logger
        , string message, params object[] args
    );
    public static void LogTrace(this ILogger logger
        , Exception exception, string message, params object[] args
    );
    public static void LogTrace(this ILogger logger
        , EventId eventId, string message, params object[] args
    );
    public static void LogTrace(this ILogger logger
        , EventId eventId, Exception exception, string message, params object[] args
    );

    public static void LogWarning(this ILogger logger
        , EventId eventId, string message, params object[] args
    );
    public static void LogWarning(this ILogger logger
        , EventId eventId, Exception exception, string message, params object[] args
    );
    public static void LogWarning(this ILogger logger
        , string message, params object[] args
    );
    public static void LogWarning(this ILogger logger
        , Exception exception, string message, params object[] args
    );
}

Func<string> を引数とするオーバーロードを追加する

ログメッセージを生成する Func<string> 型のメソッドを引数として受け取るログ出力メソッドを拡張メソッドとして実装してみます。最終的に ILogger.Log<TState> メソッドを呼び出すこととします。LogMessageBuilderState 型のインスタンスに受け取ったメソッドを格納し、ILogger.Log<TState> メソッドに渡します。

public static void LogDebug(this ILogger logger, EventId eventId, Exception exception, Func<string> messageBuilder)
{
    if (!logger.IsEnabled(LogLevel.Debug)) { return; }
    var state = new LogMessageBuilderState(messageBuilder);
    logger.Log(LogLevel.Debug, eventId, state, exception, LoggerEnvironment.LogMessageBuilderFormatter);
}
LoggerEnvironment
public static class LoggerEnvironment
{
    public static Func<LogMessageBuilderState, Exception, string> LogMessageBuilderFormatter
    {
        get { return s_LogMessageBuilderFormatter; }
        set { s_LogMessageBuilderFormatter = value; }
    }
    private static Func<LogMessageBuilderState, Exception, string> s_LogMessageBuilderFormatter = LogMessageBuilderState.Format;
}
LogMessageBuilderState
public readonly struct LogMessageBuilderState
{
    public LogMessageBuilderState(Func<string> messageBuilder)
    {
        MessageBuilder = messageBuilder;
    }

    public Func<string> MessageBuilder { get; }

    public override string ToString()
    {
        return MessageBuilder?.Invoke();
    }

    public static string Format(LogMessageBuilderState info, Exception exception)
    {
        return info.ToString();
    }
}

CallerInfo 属性を使って呼び出し元情報を付加する

CallerInfo 属性を使ったログ出力メソッドを実装してみます。

CallerInfo 属性の仕組み

CallerInfo 属性はメソッドの呼び出し元情報を引数として取得することができる属性です。コンパイラによってメソッドの呼び出しコードに値が埋め込まれるため、実行時の負荷はほとんどありません。

属性 説明
CallerMemberName 対象のメソッドを呼び出したコードが実装されているメソッドまたはプロパティ。
CallerFilePath 対象のメソッドを呼び出したコードが実装されているソースファイルのパス。
CallerLineNumber 対象のメソッドを呼び出したコードが実装されている行番号。

次のようなログ出力メソッドがあるとします。

// using System.Runtime.CompilerServices;
public static class LogUtility
{
    public static void Log(string message
        , [CallerMemberName] string callerMember = ""
        , [CallerFilePath] string callerFilePath = ""
        , [CallerLineNumber] int callerLineNumber = -1
    )
    {
        Console.WriteLine($"[{callerMember}] {message} ({callerFilePath}:{callerLineNumber})")
    }
}

このログ出力メソッドを次のように呼び出します。CallerInfo 属性にあたる3つの引数は optional ですので省略可能です。

Program.cs
namespace GenericHostSample
{
    class Program
    {
        static async Task Main(string[] args)
        {
            // LogUtility.Log("Start!", "Main", "d:\source\GenericHostSample\Program.cs", 7)
            // のようなコードとしてコンパイルされます。
            LogUtility.Log("Start!");
            await CreateHostBuilder(args).RunConsoleAsync();
        }
    }
}

コンソールには次のようなログが出力されます。

[Main] Start! (d:\source\GenericHostSample\Program.cs:7)

ILogger への組み込み

CallerInfo 属性を使ったログ出力メソッドを拡張メソッドとして実装してみます。最終的に ILogger.Log<TState> メソッドを呼び出すこととします。LogWithCallerState 型のインスタンスにログメッセージと呼び出し元情報を格納し、ILogger.Log<TState> メソッドに渡します。

フォーマットメソッドは静的フィールドに保持させておく他、ILogger が ILogFormatter<LogWithCallerState> インターフェースを実装する場合はそのフォーマットメソッドを使用するようにしています。

// using System.Runtime.CompilerServices;

public static void LogDebugWithCaller(this ILogger logger, string message, object[] args = null
    , [CallerMemberName] string callerMember = ""
    , [CallerFilePath] string callerFilePath = ""
    , [CallerLineNumber] int callerLineNumber = -1)
{
    if (!logger.IsEnabled(LogLevel.Debug)) { return; }

    var state = new LogWithCallerState(message, args, callerMember, callerFilePath, callerLineNumber);

    if (logger is ILogFormatter<LogWithCallerState> formatter)
    {
        logger.Log(LogLevel.Debug, default, state, null, formatter.Format);
    }
    else
    {
        logger.Log(LogLevel.Debug, default, state, null, LoggerEnvironment.LogWithCallerFormatter);
    }
}
ILogFormatter
public interface ILogFormatter<TState>
{
    string Format(TState state, Exception exception);
}
LoggerEnvironment
public static class LoggerEnvironment
{
    public static Func<LogWithCallerState, Exception, string> LogWithCallerFormatter
    {
        get { return s_LogWithCallerFormatter; }
        set { s_LogWithCallerFormatter = value; }
    }
    private static Func<LogWithCallerState, Exception, string> s_LogWithCallerFormatter = LogWithCallerState.Format;
}
LogWithCallerState
public readonly struct LogWithCallerState
{
    #region ctor

    public LogWithCallerState(string logmessage, object[] logMessageArgs, string callerMember, string callerFilePath, int callerLineNumber)
    {
        if (logmessage != null && logMessageArgs?.Length > 0)
        {
            LogMessage = () => string.Format(logmessage, logMessageArgs);
        }
        else
        {
            LogMessage = () => logmessage;
        }

        CallerMember = callerMember;
        CallerFilePath = callerFilePath;
        CallerLineNumber = callerLineNumber;
    }

    public LogWithCallerState(Func<string> logMessage, string callerMember, string callerFilePath, int callerLineNumber)
    {
        LogMessage = logMessage;
        CallerMember = callerMember;
        CallerFilePath = callerFilePath;
        CallerLineNumber = callerLineNumber;
    }

    #endregion

    public string CallerMember { get; }
    public string CallerFilePath { get; }
    public int CallerLineNumber { get; }
    public Func<string> LogMessage { get; }

    public override string ToString()
    {
        return $"{LogMessage?.Invoke()}|{CallerMember}|{Path.GetFileName(CallerFilePath)}|{CallerLineNumber}";
    }

    public static string Format(LogWithCallerInfo info, Exception exception)
    {
        return info.ToString();
    }
}
2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?