Serilog: ведение журналов различных типов событий журнала

К моему API обращаются разные типы потребителей. Есть внешние приложения и пользователи через веб-интерфейс.

Я попытаюсь объяснить это на примере: Итак, при вызове метода я хотел бы регистрировать, кто или что получил к нему доступ.

В случае внешнего приложения я хотел бы записать что-то вроде этого (используя шаблон):

"[{Caller}] {Timestamp:HH:mm:ss} [{Level}] (RequestId:{RequestId} | Key:{Key} | AppVersion:{Version}) {Message}{NewLine}{Exception}"

и в случае действия, инициированного пользователем, я хотел бы зарегистрировать что-то вроде:

"[{Caller}] {Timestamp:HH:mm:ss} [{Level}] FullName:{FullName} | Organization:{Organization} | AppVersion:{Version}) {Message}{NewLine}{Exception}"

Доступ к обоим типам вызывающих методов осуществляется из Thread.CurrentPrincipal.Identity, но каждый из них реализует разные типы удостоверений с разными настраиваемыми свойствами.

Мой код будет выглядеть так:

public void DoSomething()
{
    Log.Information("DoSomething called");
}

Если у меня есть регистратор, настроенный примерно так:

var logger = new LoggerConfiguration()
                   .Enrich.WithProperty("Version", appVersion)
                   .Enrich.WithProperty("Caller", caller)
                   .Enrich.With(new MyEnricher())
                   .WriteTo.ColoredConsole(outputTemplate: "[{Caller}] {Timestamp:HH:mm:ss} [{Level}] FullName:{FullName} | Organization:{Organization} | AppVersion:{Version}) {Message}{NewLine}{Exception}")
                   .CreateLogger();

он никогда не будет отображать Key и RequestId, если он будет вызван внешним приложением (идентификатор потока).

Я добавил MyEnricher в регистратор, и он выглядит примерно так:

public class MyEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var identity = Thread.CurrentPrincipal.Identity;
        if (identity is ExternalIdentity)
        {
            var externalIdentity = Thread.CurrentPrincipal.Identity as ExternalIdentity;
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Key", externalIdentity.Key));
            logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("RequestId", externalIdentity.RequestId));
        }
        else
        {
            var userIdentity = Thread.CurrentPrincipal.Identity as UserIdentity;

            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("FullName", userIdentity.FullName));
            logEvent.AddOrUpdateProperty(propertyFactory.CreateProperty("Organization", userIdentity.OrganizationName));
        }
}

Насколько я смог понять из документации и примеров в Интернете, шаблон регистрации устанавливается только в момент настройки регистратора, до его фактического создания. Мне не удалось получить доступ и изменить шаблон в enricher через LogEvent, поскольку он доступен только для чтения (имеет только геттер).

Мне известно о возможном форматировании сообщений, но в данном случае я не ищу этого.

Конечный результат, который я хотел бы видеть в журналах, для внешнего доступа к приложению выглядит примерно так:

17 января 2016 г. 10: 11: 42.524 [API] 10:11:40 [Информация] (RequestId: 123 | Key: XXX-1 | AppVersion: 1.2.1) Вызывается DoSomething

и при входе для пользователя:

17 января 2016 г. 11: 12: 42.524 [WEB] 11:12:40 [Информация] (Полное имя: Аноним | Организация: MyOrg | Версия приложения: 1.2.1) Вызывается DoSomething

Мой вопрос: как (если возможно) я могу регистрировать (и видеть это в журналах) различные типы событий с разными свойствами для входа в шаблон? Можно ли манипулировать шаблоном во время выполнения на лету? Я не хотел бы иметь шаблон со всеми возможными токенами из обоих или многих других возможных типов событий, а также их свойства, определенные в одном месте (многие из которых пусты в одном или другом случае).


person Janez Lukan    schedule 17.01.2016    source источник


Ответы (1)


Если вы можете принять немного другое форматирование из вашего примера, вы можете использовать здесь деструктуризацию:

"[{Caller}] {Timestamp:HH:mm:ss} [{Level}] ({Principal}) {Message}{NewLine}{Exception}"

Тогда в вашем enricher:

public class MyEnricher : ILogEventEnricher
{
    public void Enrich(LogEvent logEvent, ILogEventPropertyFactory propertyFactory)
    {
        var identity = Thread.CurrentPrincipal.Identity;
        if (identity is ExternalIdentity)
        {
            var externalIdentity = Thread.CurrentPrincipal.Identity as ExternalIdentity;
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Principal", new {
                externalIdentity.Key,
                externalIdentity.RequestId
            }, true));
        }
        else
        {
            var userIdentity = Thread.CurrentPrincipal.Identity as UserIdentity;
            logEvent.AddPropertyIfAbsent(propertyFactory.CreateProperty("Principal", new {
                userIdentity.FullName,
                userIdentity.OrganizationName
            }, true));
        }
    }
}

(Обратите внимание на true для запуска сериализации анонимных объектов.)

Это выведет различные свойства принципала в синтаксисе пары ключ-значение.

В противном случае можно использовать собственный ITextFormatter (код основан на DisplayFormatter от Serilog). ColoredConsole не принимает пользовательский форматер текста, но:

WriteTo.Sink(new RollingFileSink(@"Logs\app-{Date}.txt", formatter))

позволит пройти.

person Nicholas Blumhardt    schedule 19.01.2016