我正在尝试将 C# 5.0 调用方信息与 C# 参数关键字相结合。 目的是为日志记录框架创建一个包装器,我们希望记录器像 String.Format 一样格式化文本。 在以前的版本中,该方法如下所示:
void Log(
string message,
params object[] messageArgs = null);
我们这样称呼它:
log.Log("{0}: I canna do it cap'n, the engines can't handle warp {1}!",
"Scotty", warpFactor);
现在,我们希望捕获呼叫者信息并记录该信息。 因此,签名变为:
void Log(
string message,
params object[] messageArgs,
[CallerMemberName] string sourceMemberName = null);
这不会编译,因为参数必须是最后一个参数。 所以我试试这个:
void Log(
string message,
[CallerMemberName] string sourceMemberName = null,
params object[] messageArgs);
有没有办法在不提供 sourceMembername 或将 messageArgs 参数显式分配为命名参数的情况下调用它? 这样做违背了 params 关键字的目的:
// params is defeated:
log.Log("message",
messageArgs: new object[] { "Scotty", warpFactor });
// CallerMemberName is defeated:
log.Log("message", null,
"Scotty", warpFactor);
有没有办法做到这一点? 似乎传递调用方信息的"黑客"方式排除了使用 params 关键字。 如果 C# 编译器认识到调用方成员信息参数根本不是真正的参数,那就太棒了。 我认为没有必要明确地通过它们。
我的备份将是跳过 params 关键字,并且调用者将不得不在最后一个示例中使用长签名。
我不认为它可以完全按照你想要的方式完成。但是,我可以想到一些可行的解决方法,它们可能会给您带来几乎相同的好处。
-
使用中间方法调用来捕获调用方成员名称。第一个方法调用返回一个委托,该委托又可以调用以提供其他参数。这看起来很奇怪,但它应该有效:
log.Log()("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", warpFactor);
这里的一个缺点是可以调用
log.Log("something")
,期望您的消息将被记录,并且不会发生任何事情。如果使用 Resharper,则可以通过向Log()
方法添加[Pure]
属性来缓解此问题,以便在有人未对生成的对象执行任何操作时收到警告。您也可以稍微调整一下这种方法,例如:var log = logFactory.GetLog(); // <--injects method name. log("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", warpFactor);
-
使用 lambda 生成日志消息,并让字符串。格式负责参数数组:
log.Log(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", warpFactor));
这是我通常使用的方法,它有一些附带的优点:
- 您的 log 方法可以捕获在生成调试字符串时生成的异常,因此您不会破坏系统,而只会收到一个错误:"无法生成日志消息:[异常详细信息]"。
有时,传递给格式字符串的对象可能会产生额外的成本,您只想在需要时产生这种成本:
log.Info(() => string.Format("{0}: I canna do it cap'n, the engines can't handle warp {1}!", _db.GetCurrentUsername(), warpFactor));
如果未启用信息级日志记录,则不希望上述代码执行数据库行程。
作为旁注,我发现自己使用字符串。格式足够频繁,以至于我创建了一个帮助程序方法来稍微缩短语法:
log.Log(() => "{0}: I canna do it cap'n, the engines can't handle warp {1}!" .With("Scotty", warpFactor));
要同意StriplingWarrior的建议而不是委托,您可以使用流畅的语法来完成。
public static class Logger
{
public static LogFluent Log([CallerMemberName] string sourceMemberName = null)
{
return new LogFluent(sourceMemberName);
}
}
public class LogFluent
{
private string _callerMemeberName;
public LogFluent(string callerMamberName)
{
_callerMemeberName = callerMamberName;
}
public void Message(string message, params object[] messageArgs)
{
}
}
然后像这样称呼它
Logger.Log().Message("{0}: I canna do it cap'n, the engines can't handle warp {1}!", "Scotty", 10);
记录器不一定是静态的,但这是一种演示概念的简单方法
好吧,让我提一个选项; 您可以使用反射来获得与CallMemberName
完全相同的名称。这肯定会更慢。假设你不是每一毫秒都记录,我相信它足以处理压力。
var stackTrace = new StackTrace();
var methodName = stackTrace.GetFrame(1).GetMethod().Name;
按照Jim Christophers(@beefarino(的Pluralsight课程来设置我自己的日志记录项目。为此,我将 ILog 接口克隆为 ILogger,我实现了它 - 假设在一个名为 LoggerAdapter 的类中 - 然后我使用 Jim Christophers LogManager 拥有一个 GetLogger(Type type(-方法,该方法返回一个包装的 log4net-logger 'LoggerAdapter':
namespace CommonLogging
{
public class LogManager : ILogManager
{
private static readonly ILogManager _logManager;
static LogManager()
{
log4net.Config.XmlConfigurator.Configure(new FileInfo("log4net.config"));
_logManager = new LogManager();
}
public static ILogger GetLogger<T>()
{
return _logManager.GetLogger(typeof(T));
}
public ILogger GetLogger(Type type)
{
var logger = log4net.LogManager.GetLogger(type);
return new LoggerAdapter(logger);
}
}
}
下一步是创建一个像这样的泛型扩展,将调用方信息设置为 ThreadContext.Property:
public static class GenericLoggingExtensions
{
public static ILogger Log<TClass>(this TClass klass, [CallerFilePath] string file = "", [CallerMemberName] string member = "", [CallerLineNumber] int line = 0)
where TClass : class
{
ThreadContext.Properties["caller"] = $"[{file}:{line}({member})]";
return LogManager.GetLogger<TClass>();
}
}
有了它,我总是可以在编写实际方法之前调用记录器,只需调用:
this.Log().ErrorFormat("message {0} {1} {2} {3} {4}", "a", "b", "c", "d", "e");
如果您将模式布局的转换模式配置为使用该属性:
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%utcdate [%thread] %-5level %logger - %message - %property{caller}%newline%exception" />
</layout>
您将始终有一个正确的输出,其中包含第一个 .日志((-调用:
2017-03-01 23:52:06,388 [7] ERROR XPerimentsTest.CommonLoggingTests.CommonLoggingTests - message a b c d e - [C:gitmineexperimentsXPerimentsTestCommonLoggingTestsCommonLoggingTests.cs:71(Test_Debug_Overrides)]