Problem

世の中にロギングライブラリはたくさんあります。有名どころでは log4net, NLog とかです。

単純にこれらのロギングライブラリをアプリにリンクすると、万が一ロギングライブラリに問題が生じたり、ログの出力先をWebServiceに投げたい、とかなったらログ出力を記述している個所を全部書き換える必要があります。
(NLogはWebServiceとかDatabaseへ出力することもできますが…)

なので、これらのライブラリとの依存性を減らすべく、ラップするなりします。ましてや、Dependency Injectionならなおさらです。
でも、ここで気づきます。



あれ?ログに出力される呼び出し元メソッド名が全部ラッパーになる\(^o^)/

Solution

幸いにして、log4net、NLogともに解決策があります。
問題は、ログ出力メソッドの呼び出し元を正しく出力することです。

通常は、各ライブラリのメソッドをダイレクトに呼び出しているメソッドのクラスが基点になります。
ですが、ラッパーをかましている場合は、ライブラリのログ出力メソッドを呼び出している箇所との間にラッパーが存在します。

NLogの場合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public interface ILogService
{

void Info(string message);

void Info(Exception exception, IFormatProvider formatProvider, string message);

}

public interface ILogFactoryService
{

ILogService Create();

ILogService Create(string name);

}

public sealed class NLogLogService : ILogService
{

#region フィールド

private readonly Logger _Logger;

#endregion

#region コンストラクタ

internal NLogLogService(Logger logger)
{
this._Logger = logger;
}

#endregion

#region メソッド

private void Write(LogLevel logLevel, string message)
{
this._Logger.Log(typeof(NLogLogService), LogEventInfo.Create(logLevel, this._Logger.Name, message));
}

private void Write(LogLevel logLevel, Exception exception, IFormatProvider formatProvider, string message)
{
this._Logger.Log(typeof(NLogLogService), LogEventInfo.Create(logLevel, this._Logger.Name, exception, formatProvider, message));
}

#endregion

#region ILogService メンバー

public void Info(string message)
{
this.Write(LogLevel.Info, message);
}

public void Info(Exception exception, IFormatProvider formatProvider, string message)
{
this.Write(LogLevel.Info, exception, formatProvider, message);
}

#endregion

}

public sealed class NLogLogFactoryService : ILogFactoryService
{

#region ILogFactoryService メンバー

public ILogService Create()
{
return new NLogLogService(LogManager.GetCurrentClassLogger());
}

public ILogService Create(string name)
{
return new NLogLogService(LogManager.GetLogger(name));
}

#endregion

}

上がラッパーになります。
ポイントは、Logメソッドになります。

第一引数にラッパークラスの型を渡します。これにより、呼び出し元のメソッドを本来の意図した場所に訂正できます。
スタックトレースを辿っていき、第一引数に渡したクラスの直前の箇所を呼び出し元として判定しているのでしょう。

使い方は下記のようになります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public sealed class TestService
{

#region フィールド

private readonly ILogService _LogService;

#endregion

#region コンストラクタ

public TestService(ILogFactoryService logFactoryService)
{
this._LogService = logFactoryService.Create("MainLogger");
}

#endregion

#region メソッド

public void Test()
{
this._LogService.Info("Test start");
}

#endregion

}

ログを出力したいクラスのコンストラクタでILogFactoryService.Createメソッドを呼び出し、ILogServiceを生成します。
実際には内部でNLogのロガーが生成されていますが、それらを隠蔽しています。

log4net

同じように、Logメソッドでラッパーを指定します。
詳細は下記を参照。