используя(), получите доступ к данным от родительского вызываемого абонента для целей ведения журнала

#c# #visual-studio #design-patterns

Вопрос:

У меня есть настраиваемая структура журнала сборки, которая регистрируется в базе данных.

Например, это может сделать

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

Это прекрасно работает для общего использования, но приложение довольно сложное, и существует множество различных частей, которые вызываются из основного кода. Например, последовательность входа в систему может отправить SMS для OTP, которое обрабатывается совершенно другой частью системы, и нет смысла передавать много значений только для целей регистрации.

Чего я хочу добиться, так это пометить журнал, например, идентификатором пользователя, чтобы я мог искать все, что связано с этим конкретным пользователем. Я также хочу отметить любую регистрацию в модуле SMS, даже если модуль SMS ничего не знает о концепции пользователя.

Поэтому я думаю о том, можно ли получить текущий идентификатор потока и сохранить некоторые вещи, касающиеся регистрации на более высоком уровне. Интересно, возможно ли это вообще?

Код блока питания:

 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? 
        }
    }
}
 

С уважением
, Йенс

Комментарии:

1. Что такое L ? Что же SetUser делать? Можете ли вы установить в нем какое-нибудь свойство, которое можно будет прочитать позже?

Ответ №1:

Можете ли вы показать нам некоторые фрагменты из L ? Это что static , урок? SetUser Устанавливает ли static переменную? Вы могли бы использовать using блок так, как вы предлагаете. Вы хотели бы реализовать IDisposable и очистить UserID значение в Dispose методе. Но если UserID это static переменная, то это решение не будет работать в многопоточной среде (без некоторых других изменений). И дизайн просто кажется мне странным.

В целом кажется, что вы используете 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"); 
        }
    }
}
 

Введите в регистратор идентификатор пользователя, когда эта информация будет доступна:

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

Ответ №2:

Оператор using не предназначен для такого использования. Оператор using был введен, чтобы иметь возможность определять ограниченную область действия и в то же время обеспечивать удаление объектов с помощью интерфейса IDisposable (см. также https://docs.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 и сопоставить его с идентификатором потока, а затем в функциях проверить, существует ли определенный контекст журнала для этого потока, когда вы что-то регистрируете. В реализации интерфейса IDisposable объекта LogContext вы должны затем удалить контекст (который соответствует концу вашей области использования ()). Я бы, однако, не стал этого делать, потому что это «скрывает» кучу логики, но, более того, оно основывается на том факте, что каждая функция будет выполняться в одном потоке. Это в сочетании со скрытием этого делает его возможным источником ошибок (если пользователь кода не знает, что это связано с потоком, и изменяет поток, вы можете пропустить информацию, сделать неправильные предположения на основе ведения журнала и т. Д.). Я думаю, что это неплохая практика, если у вас есть такие функции, как SMS-менеджер, который имеет ряд вспомогательных функций, для передачи в определенном контексте объекта.

Кроме того, имейте в виду, что эта концепция существует в большинстве популярных библиотек журналов, таких как Serilog, и почти во всех случаях написание собственных библиотек журналов не является самым прибыльным бизнесом (поскольку большинство из этих библиотек также имеют расширения, позволяющие вам писать пользовательский приемник, который, например, затем записывал бы выходные данные журнала в базу данных для вашего конкретного сценария (но все остальное вы получаете бесплатно).