Логирование абстрактных типов используя значения конкретных типов в C#
Есть сервис, читающий сообщения из очереди. Необходимо реализовать логирование некоторых полей приходящих сообщений. Сообщения - разных типов с разным набором полей. Как это выглядит прямо сейчас:
Есть интерфейс IMessage, у которого нет ни свойств, ни методов.
Есть реализации IMessage (названия полей и классов - не настоящие, созданы только для демонстрации примера).
AlertMessage
public class AlertMessage: IMessage { public string AlertHeader { get;set; } public string AlertId { get;set; } // Здесь еще много других полей }StatusMessage
public class StatusMessage: IMessage { public string StatusId { get;set; } public double Speed { get; set; } // Здесь еще много других полей }
Код обработки сообщений:
public async Task ReceiveAndProcessMessagesAsync()
{
IReadOnlyCollection<IMessage> messages;
do
{
messages = await _receiveService.ReceiveMessagesAsync();
foreach (var message in messages)
{
try
{
var messageProcessor = GetMessageProcessor(message);
await messageProcessor.ProcessMessageAsync();
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during processing.");
}
finally
{
await message.MarkAsProcessedAsync();
if (_logger.IsEnabled(LogLevel.Debug))
{
_logger.LogDebug("Message processed. {MessageType}. {@Message}", message.GetType().Name, message);
}
else
{
_logger.LogInformation("Message processed. {MessageType}", message.GetType());
}
}
}
} while (messages.Count > 0);
}
private IMessageProcessor GetMessageProcessor(IMessage message)
{
return message switch
{
AlertMessage alertMessage => new AlertMessageProcessor(alertMessage),
StatusMessage statusMessage => new StatusMessageProcessor(statusMessage),
//...
_ => throw new ArgumentOutOfRangeException("Unknown message.")
};
}
Мне нужно в блоке finally в строке:
_logger.LogInformation("Message processed. {MessageType}", message.GetType());
дополнительно логировать поля сообщений. Т.е. чтобы
- для AlertMessage было сообщение
Message processed. {MessageType}, AlertId = {AlertId}, AlertHeader = {AlertHeader}
- для StatusMessage было сообщение
Message processed. {MessageType}, StatusId = {StatusId}, Speed = {Speed}
Как можно реализовать такое поведение? Пытаюсь найти каноническое решение в интернете, но не знаю, как правильно загуглить. Можно сделать метод типа LogMessageProcessed(IMessage message), там сделать такой же switch-case, что и в GetMessageProcessor(IMessage message), и в зависимости от типа логировать по-разному. Это первое, что мне приходит в голову. Еще приходит в голову сделать декоратор для процессоров сообщений с логированием.
Ответы (1 шт):
Можно реализовать с помощью рефлексии.
Допустим есть такой класс:
public class Message
{
public string Text { get; set; }
public int Id { get; set; }
}
Вот так можно вытянуть свойства:
var message = new Message() { Text = "текст", Id = 0 }; // пример объекта
var propertiesText = message
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) // выбираете нужные поля
.Select(property => property.Name + " = " + property.GetValue(message))
.ToList();
Console.WriteLine(string.Join(", ", propertiesText));
Вывод: Text = текст, Id = 0
Ну и это всё конечно можно свернуть в метод или метод-расширение и потом легко использовать:
public static string GetPropertiesValuesString<T>(this T obj)
{
var propertiesValues = obj
.GetType()
.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public) // выбираете нужные поля
.Select(property => property.Name + " = " + property.GetValue(obj))
.ToList();
return string.Join(", ", propertiesValues);
}