使用(),访问父被调用者的数据以进行日志记录



我有一个自定义的构建日志框架,可以记录到数据库。

例如,它可以进行

L.e("Error invalid password", userGuid);

这对于一般用途来说很好,但应用程序相当复杂,并且从主代码中调用了许多不同的部分。例如,登录序列可以发送OTP的SMS,OTP由系统的另一个部分处理,仅仅为了登录目的传递大量值是没有意义的。

我想要实现的是用例如userGuid标记日志,这样我就可以搜索与该特定用户相关的所有内容。我还想标记SMS模块中的任何登录,即使SMS模块对用户概念一无所知。

所以我想的是,是否有可能获得当前的线程ID,并在更高级别上存储一些关于日志记录的内容。我想知道这是否可能。

Psuedo代码:

void Login(UserName, Password) {
User user = UserManager.GetUser(UserName)
using(L.SetUser(user.ID)) {   //Here I want to use user.ID later in code that dont know the context
SmsManager.SendOtp(user.Phonenumber)
}
}
public class SmsManager {
public static void SendOtp(string phonenumber) {
if (phonenumber == "") {
L.error("Phone number is empty");   //How can I use the value from L.SetUser above? Could I use a hash table of threadids in L or would that be a bad idea? 
}
}
}

亲切问候Jens

你能给我们看一些L的片段吗?那是static级吗?SetUser是否设置了static变量?您可以按照建议的方式使用using块。您想要实现IDisposable并清除Dispose方法中的UserID值。但是,如果UserIDstatic变量,则此解决方案将无法在多线程环境中工作(如果没有其他更改(。这个设计在我看来很奇怪。

总的来说,您似乎经常使用static。这可能会给你带来麻烦。

有很多可能的解决方案。如果没有看到更多的代码,很难说什么是最好的。这里有一种使用依赖项注入的方法,可以根据需要将模块分离。

为您的记录器定义一个接口。

public interface ILogger
{
void Error(string message);
}

使用添加用户信息的类实现:

public class MessageWithUserLogger : ILogger
{
private readonly string _userId;
public MessageWithUserLogger(string userId)
{
_userId = userId;
}
public void Error(string message)
{
L.error(message, _userId);
}
}

SmsManager类更改为非static,并依赖于ILogger抽象而非L实现:

public class SmsManager
{
private readonly ILogger _logger;
public SmsManager(ILogger logger)
{
_logger = logger;
}
public void SendOtp(string phonenumber)
{
if (phonenumber == "")
{
_logger.Error("Phone number is empty"); 
}
}
}

当用户ID信息可用时,为记录器注入该信息:

void Login(UserName, Password)
{
User user = UserManager.GetUser(UserName);
ILogger logger = new MessageWithUserLogger(user.ID);
SmsManager smsManager = new SmsManager(logger);
smsManager.SendOtp(user.Phonenumber);
}

using语句不是这样使用的。引入using语句是为了能够定义有限的范围,同时确保使用IDisposable接口处理对象(另请参阅https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-statement)。

使用using语句的方式使属性看起来像是在启动作用域时发送的,并且在某种程度上是";未设置";之后,但事实并非如此。

当使用记录器并从伪代码开始时,我认为应该扩展日志框架,以便在使用时创建特定于上下文的记录器,然后将日志上下文传递给静态函数。它看起来如下:

void Login(UserName, Password) {
User user = UserManager.GetUser(UserName)
using(var logContext = L.CreateContext(user.ID)) {   //Here I want to use user.ID later in code that dont know the context
SmsManager.SendOtp(logContext, user.Phonenumber)
}
}
public class SmsManager {
public static void SendOtp(LogContext logContext, string phonenumber) {
if (phonenumber == "") {
logContext.error("Phone number is empty");   //How can I use the value from L.SetUser above? Could I use a hash table of threadids in L or would that be a bad idea? 
}
}
}

理论上可以将上下文存储在L对象中,并将其映射到线程ID,而不是传递日志上下文,稍后在函数中,当您记录某些内容时,检查该线程是否有特定的日志上下文。在LogContext对象的IDisposable接口实现中,您应该移除上下文(对应于using((作用域的末尾(。然而,我不会这么做,因为它";隐藏";一堆逻辑,但更重要的是,它依赖于每个函数都将在同一个线程中执行的事实。这与隐藏这一点相结合,使其成为错误的可能来源(如果代码的用户不知道这与线程链接,并更改了线程,则可能会错过信息,根据日志记录做出错误的假设,等等(。我认为,如果你有像SMS管理器这样的函数,它有很多助手函数,可以传递特定于上下文的对象,这是一种不错的做法。

此外,请注意,这是一个存在于大多数流行的日志库(如Serilog(中的概念,并且在几乎所有情况下,编写自己的日志库并不是最有利可图的业务(因为这些库中的大多数都有扩展,允许您编写自定义接收器,例如,它会将日志输出写入特定场景的数据库中(但其余的都是免费的(。

最新更新