Как стать автором
Обновить

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

Глобальный логгер — это объект, через который осуществляется логирование в Serilog

В том-то и дело, что нет. Логирование в Serilog осуществляется через ILogger, который не глобальный. Их можно иметь несколько, и все будет прекрасно. Log.Logger — это всего лишь точка доступа к одному из них. И про нее можно сказать банальное "просто не делайте так". Начиная с того, что прикладному разработчику вообще не обязательно давать доступ к серилогу, и заканчивая тем, что сделайте статический анализатор, который бросает ошибку при обращении к Log.Logger.


У меня тут под боком гигантский проект, внутри которого смесь Framework и Standard, три разных слоя логирования, включая Serilog, и там Log.Logger используется только косвенно, легаси-адаптерами в виде LogLib.


(При этом, на самом деле, bootstrap logger — это не такое давнее добавление к Serilog, и оно нужно далеко не всегда, если вы понимаете, что и зачем вы делаете.)


Отдельно немножко про тесты:


public async Task<HttpResponseMessage> Send()
{
var client = _webAppFactory.WithWebHostBuilder(b => b.UseSerilog(
(context, config) => config
.WriteTo.Logger(new TestLogger(_logPrefix, _output))
)).CreateClient();
return await client.SendAsync(new HttpRequestMessage(HttpMethod.Get, "/ping"));
}
}


В методе Send инициируется приложение с добавлением Serilog и логированием в тестовый логгер.

Неправильно инициализируется, оттуда и проблемы. Зачем же вы в тестах используете оверлоад, который работает с Log.Logger, когда есть оверлоад, который примет ваш логгер снаружи, тем самым сняв все проблемы со множественными логгерами (и жизненным циклом)?

Собственно, вот это:


Разработчики Serilog предлагают смешанный подход к использованию Serilog в .NET Core приложениях: [...] с использованием глобального логгера через свойство Logger публичного статического класса Serilog.Log.

вы откуда взяли? (кроме примера для Program.cs)


У Serilog еще во времена .NET Framework была мощная интеграция с DI (например, с Autofac), и использовать Log.Logger не было необходимости даже тогда.

Я вот с Serilog никогда не работал. Есть толковый how-to? (по возможности — не для веб приложений).

А с Microsoft.Extensions.Logging работали, или структурированное логирование вообще в новинку?

Только те самые how-to и видел по структурированному. Выглядит круто, но в жизни не сталкивался.

Окей, а что-нибудь в вашей существующей инфраструктуре зависит от Microsoft.Extensions.Logging? И есть ли у вас DI, или он вам тоже низачем не был нужен?

Ни того ни другого толком, логи сейчас — просто NLog синглтоном =)

Если у вас нет DI, то и с серилогом вы будете точно так же работать, через синглтон, как в самом базовом примере в статье — особо вариантов-то нет.

вы откуда взяли? (кроме примера для Program.cs)

В readme на странице библиотеки для интеграции Serilog в .NET Core serilog-aspnetcore такой код присутствует почти в самом начале в разделе Instructions. И когда программист знакомится с Serilog и заходит на эту страницу, то он расценивает это как рекомендацию. Разработчики сразу со страницы readme библиотеки практически заявляют: вот инструкция — делай так:
  • используй Log.Logger
  • интегрируй Serilog в подсистему логирования .NET Core.

И программисты так и будут делать. Они будут копипастить этот код. И будут привыкать так делать из проекта в проект. Если будет опытный наставник вроде Вас — хорошо. Но далеко не всем так везёт.
В readme на странице библиотеки для интеграции Serilog в .NET Core serilog-aspnetcore такой код присутствует почти в самом начале в разделе Instructions.

Это и есть пример для Program.cs. Пример обращения к Log.Logger из другого места программы есть?


Вы, кстати, понимаете, почему вброшенный через DI ILogger не равен Log.Logger?

Пример обращения к Log.Logger из другого места программы есть?

Мне кажется, что есть пример обращения к Log.Logger в принципе — и этого уже достаточно.
Мне кажется, что есть пример обращения к Log.Logger в принципе — и этого уже достаточно.

Вам, возможно, и достаточно. Другим разработчикам — не обязательно.

Вы, кстати, понимаете, почему вброшенный через DI ILogger не равен Log.Logger?

Да, Я упустил тот момент, что это не тот же самый объект. Но вброшенный объект — это обёртка над объектом из Log.Logger, которая всё равно передаёт соощение тому же глобальному логгеру, как Я понял.
Но вброшенный объект — это обёртка над объектом из Log.Logger, которая всё равно передаёт соощение тому же глобальному логгеру, как Я понял.

Вы поняли неправильно. Log.Logger — это всего лишь место, куда можно положить любой ILogger. Вот реально любой. У меня там лежит логгер со специальным контекстом, чтобы можно было ловить все записи.

Да Я понимаю, что если после добавления Serilog в подсисетму логирвоания .NET Core, присвоить в Log.Logger другой объект, то он не будет никак связан с тем, который будет использоваться подсистемой логирвоания .NET Core. Я думал, что не надо будет об этом писать — это итак очевидно.
Но сразу после интеграции, это именно так, как Я написал. Я это проверил, прежде чем отвечать на Ваш комментарий.
Но сразу после интеграции, это именно так, как Я написал.

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


А в интеграции, которая сделана у меня, в Log.Logger никогда не оказывается тот же объект, который вбрасывается в DI.

которые вы сочли нужными.


Которые мне предлагют сделать разработчики Serilog в примере на главной странице провекта в github.

Мммм. Я пошел по вашей ссылке, там написано вот такое:


Host.CreateDefaultBuilder(args)
                .UseSerilog() // <-- Add this line
                .ConfigureWebHostDefaults...

А у вас в посте — вот такое:


Host.CreateDefaultBuilder(args)
    .UseSerilog((context, services, configuration) => configuration
                .ReadFrom.Configuration(context.Configuration)
                .ReadFrom.Services(services)
                .Enrich.FromLogContext()
                .WriteTo.Console())
    .ConfigureWebHostDefaults...

Есть некоторая разница.


Но не суть, на самом деле. Все равно, вы, как разработчик, решаете, какую конфигурацию использовать. Если вы не думаете над тем кодом, который вы используете, его поведение будет для вас непредсказуемым.

Есть некоторая разница.

Я брал код из примера, на который есть ссылка с этой же страницы.

Если вы не думаете над тем кодом

К сожадению, многие просто копипастят и не думают.
Я брал код из примера, на который есть ссылка с этой же страницы.

Ну то есть видите, примеров было несколько, вы выбрали тот, который сочли нужным. Я и говорю — ваш собственный выбор.


Если я ничего не путаю, тот оверлоад, который приведен в первом примере, никак не заменяет Log.Logger (что логично, потому что логгер не меняется).


К сожадению, многие просто копипастят и не думают.

Ну в это случае, когда программа ведет себя не так, как они ожидают, виноваты они сами. Потому что, внезапно, программирование предполагает думать.

И про нее можно сказать банальное «просто не делайте так»

Мне кажется, такое обычно не работает. Я много раз видел, кода различные библиотеки (или даже стандартные классы) используют совсем не так, как это нужно делать просто потому, что «так проще», «и так работает», «я просто скопипастил из интернета».
Мне кажется, такое обычно не работает.

Вот для этого и есть статические анализаторы, которые фейлят билд, если не так, как надо. В случае с Serilog и Roslyn это прям просто.

Их можно иметь несколько, и все будет прекрасно

Да, сделать можно — согласен. Но в подсистеме логирования .NET Core так не делается. И Serilog внедряет туда только один ILogger объект.

Но в подсистеме логирования .NET Core так не делается.

Что такое "подсистема логирования .NET Core"?

В этом случае утверждение "в подсистеме логирования .NET Core так [иметь несколько логгеров] не делается" неверно. Вот как раз в этой "подсистеме" — а, точнее, говоря, в Microsoft.Extensions.Logging — логгеров заведомо множество.

Это не одно и тоже. Вы предлагаете создать несколько логгеров. А потом что с ними делать? Пихать в DI? В подсистеме лоигрования регистрируются не сами логгеры, а их провайдеры.
Это не одно и тоже.

Что конкретно "не одно и то же"?


Вы предлагаете создать несколько логгеров.

Нет, не предлагаю. Их создает Microsoft.Extensions.Logging.


В подсистеме лоигрования регистрируются не сами логгеры, а их провайдеры.

Именно. Поэтому ваше утверждение и неверно.

Нет, не предлагаю

Ок, Я Вас не так понял.
прикладному разработчику вообще не обязательно давать доступ к серилогу

К Log.Logger всегда есть доступ — на то он и глобальный логгер. Если Я правильно понял, и речь идёт про обёртку над Serilog, то это напоминает работу с полуфабрикатом. Он такой неудобный/непонятный, что приходится делать обёртку, чтобы ограничить другим разработчикам доступ, и чтобы они там не натворили чего лишнего. Или написать анализатор кода с той же целью (как Вы предложили).
К Log.Logger всегда есть доступ — на то он и глобальный логгер.

Нет, только если у разработчика есть доступ к сборке Serilog, что совершенно не обязательно.

Библиотека Serilog.AspNet.dll использует Serilog.dll. Поэтому в комилирующемся проекте, если используется первая библиотека, то и вторая — тоже. В этом случае доступ к сборке Serilog не ограничить. Если знаете способ — поделитесь.

Я знаю простой способ: не референсить Serilog.AspNet.dll (который вам нужен только в composition root) из проектов, реализующих прикладную логику. И все. Они даже знать не будут, что у вас в проекте серилог.

Если не использвать фич Serilog, то да, такй вариант хорош.

Стоп, а какие именно фичи серилога вам нужны?..

Например, это:

LogContext.PushProperty(..., ...);

Вы в этот момент обходите ту самую "подсистему логирования .NET Core", про которую вы только что писали, и в которой для этого есть полностью работающий механизм в виде log scopes. И после этого вы удивляетесь, что получаете смешанное поведение?


Поясню: когда вы так делаете, ваши изменения влияют только на логгеры серилога, и только на те из них, у которых сделано Enrich.FromLogContext. При этом логируете вы, по вашему же заверению, через Microsoft.Extensions.Logging.ILogger — за которым может быть любой логгер, не только серилог. Это типичное такое нарушение инкапсуляции, не надо так делать, вы можете получить неконсистентные логи.

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

  1. Неочевидно, что для тестов мне следует использовать какой-то специальный оверлоад. При использовании других логгеров в .NET Core не приходится использовать какие-то особые оверлоады. Там одинаково работает как в тестах, так и вне их.
  2. Такой оверлоад всё равно приводит к ситуации с двумя логгерами: один в Log.Logger, а другой в подсистеме логирования .NET Core (в контексте использования примера от разработчиков Serilog).

Неочевидно, что для тестов мне следует использовать какой-то специальный оверлоад.

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


Такой оверлоад всё равно приводит к ситуации с двумя логгерами: один в Log.Logger, а другой в подсистеме логирования .NET Core

Какая вам разница, что в Log.Logger? Не используйте его.

Проблема не в том, что Я не смог разобраться как работает Srilog или не справился с его использованием. Я пытаюсь донести, что как разрабочик .NET Core Я не должен видеть и иметь доступ к тому, что мне не следует использовать, но Я могу это использовать и мало того, мне ещё показывают в самом видном месте (в readme.md) как это делать, наводя меня на ложный путь. Вы об этом сами уже несколько раз упомянули — «Не используйте его». Тогда зачем он там нужен, если его не следует использовать? Значит библиотека Serilog для .NET Core должна быть иного содержания — адаптированая для API логирования .Net Core. А вместо этого мы имеем спорным образом прилепленный Serilog. Ещё раз уточню: Я не спорю, что есть технические возможности сделать всё нормально и правильно. Но это должно быть естественно и прозрачно/понятно. А не так, что надо бить по рукам или писать анализаторы кода.
Тогда зачем он там нужен, если его не следует использовать?

Для обратной совместимости.


Значит библиотека Serilog для .NET Core должна быть иного содержания

Я боюсь, у нас тут с вами фундаментальное разногласие. Я вот считаю, что библиотека Serilog ничего вам не должна.


адаптированая для API логирования .Net Core.

А зачем вам вообще серилог, если вы используете Microsoft.Extensions.Logging?


Но это должно быть естественно и прозрачно/понятно.

Мне все прозрачно и понятно.

Я боюсь, у нас тут с вами фундаментальное разногласие. Я вот считаю, что библиотека Serilog ничего вам не должна.


Это действительно так. Я никому ничего не навязываю. Ничего не требую. Я рассуждаю о дизайте Serilog с точки зрения программиста .NET Core. Разве программисту .NET Core нужно думать о какой-то совместимости? О какой? .NET Core 3.1 с 1.0 если только.

PS: Отличнй ответ на все тикеты в github Serilog )))
библиотека Serilog ничего вам не должна.
Я рассуждаю о дизайте Serilog с точки зрения программиста .NET Core. Разве программисту .NET Core нужно думать о какой-то совместимости? О какой? .NET Core 3.1 с 1.0 если только.

Я не знаю, кто такой "программист .NET Core" (хотя и считаю, что думать надо любому программисту). А я вот, как программист, который пишет и под Core, и под .NET Framework, регулярно думаю о переносимости моего кода, и очень благодарен разработчикам Serilog, которые сделали так, что я могу взять свой код от .NET Framework, перенести его под Core, и он будет работать один-в-один.

Глобальный логгер это ж legacy для обратной совместимости. Его нигде не нужно использовать.
При старте нужно использовать вывод в консоль минуя любое логгирование, т.к. если сервис упал при старте, значит неисправна инфраструктрура и вы всё равно будете смотреть вывод в консоль упавшего контейнера
Если приложение запускается только в контейнерах (как бывает в 95% случаев), то да, согласен. Но бывают и иные ситуации — например, у нас одно из production-окружений — это винда с IIS-ом (требование заказчика). И при запуске приложения там вывод в stdout так просто не увидеть.

Конечно, и там можно поглядеть в журнале событий, почему приложение не стартует — но в файле всё-таки быстрее. Поэтому добавление такого try-catch, как в примере статьи, всё-таки не лишено смысла, и иногда нас сильно выручало.
Глобальный логгер это ж legacy для обратной совместимости

Если вообще, то — да. Но контекст статьи – это .NET Core. Какое legacy Вы имеете в виду?
Интеграция Serilog с .NET Core в виде библиотеки serilog-aspnetcore появилась для .NET Core 2.0. Легаси тут может быть, наверное, если его писали под .NET Core 1 или Standard того времени. Правда, Я так и не раскопал, как обстояли дела с интеграцией Serilog с этими платформами на тот момент.

Его нигде не нужно использовать.

Опять же. Если самоконтроль — хорошо. Но когда приходится работать с другими людьми, то надо бить по рукам или, как предложил lair, писать статический анализатор кода.
Интеграция Serilog с .NET Core в виде библиотеки serilog-aspnetcore появилась для .NET Core 2.0.

serilog-aspnetcore — это не библиотека, это репозиторий. Из этого репозитория собирается NuGet-пакет Serilog.AspNetCore, который тоже не библиотека. А "библиотека" — это лежащая в этом пакете сборка (assembly) Serilog.AspNetCore.dll. И в этой библиотеке, внезапно, нет никакого глобального логгера. Вы просто не сможете его найти в исходниках.


А есть он в библиотеке Serilog, которая (а) была разработана до официального появления .NET Core и (б) хочет сохранять совместимость с теми своими пользователями, которые пользуются ей за пределами ASP.NET Core. Это и есть легаси.


Ну и на самом деле, что тоже немаловажно, Serilog.AspNetCore — это не для интеграции Serilog с .NET Core, как вы пишете. Это для интеграции с ASP.NET Core (и название об этом говорит, и первая строка описания). Нет никакого пакета для интеграции Serilog с .NET Core, не с чем там интегрироваться, просто берите базовый Serilog и используйте.

Справедливости ради замечу, что MS путается в именах .NET Core и ASP.NET Core — например, Configuration in ASP.NET Core, справедливая для .NET Core вообще (уж не знаю, приплетать ли сюда универсальный хост). Так что пакет с именем AspNetCore может годиться и для просто Core.
Так что пакет с именем AspNetCore может годиться и для просто Core.

Не в этом случае. У него в .NETStandard зависимости от Microsoft.AspNetCore.Http.Abstractions. Для "просто Core" есть Serilog.Extensions.Hosting.

serilog-aspnetcore — это не библиотека

Да, пожалуй, Я не совсем правильно выразился. Но думаю, что все (и Вы тоже) меня правильно поняли.
А есть он в библиотеке Serilog

Да, всё так. И nuget с этой библиотекой подгружается в проект, как зависимость nuget пакета Serilog.AspNetCore. Принося с собой и глобальный логгер, не смотря на то, что он там не нужен.
Принося с собой и глобальный логгер, не смотря на то, что он там не нужен.

Это он вам не нужен.

Это он разработчику .NET Core не нужен.

Это утверждение в общем случае (т.е. для всех разработчиков, пишущих под Core) неверно.

хочет сохранять совместимость с теми своими пользователями, которые пользуются ей за пределами ASP.NET Core. Это и есть легаси.

Теперь понятно о каком легаси шла речь. Это понятное желание. И, если смотреть на картину в общем, то понятно почему это всё доступно и в .Net Core. Но разработчик .Net Core не виноват, что разрабочику Serilog надо там какое-то легаси поддерживать, поэтому надо дополнительно разбираться где ему предлагают использовать что-то сомнительное и какие оверлоады методов интеграции Serilog надо вызывать, чтобы всё работало норм. Уверен, что если приложить больше желания, то дизайн Serilog для .NET Core мог быть сильно лучше, даже при условии сохранения совместимости для .NET Framework проектов.
Уверен, что если приложить больше желания, то дизайн Serilog для .NET Core мог быть сильно лучше,

Прекрасно. Приложите это желание. По моему опыту, Блумхардт прекрасно принимает мотивированные pull requests.


дизайн Serilog для .NET Core

Нет никакого "Serilog для .NET Core". Его просто нет. Есть Serilog, который сам по себе, есть адаптер серилога для Microsoft.Extensions.Logging, есть расширения этого адаптера для ASP.NET Core.

Прекрасно. Приложите это желание.

Я уже писал в статье, что там нужны слишком концептуальные изменения. Одним только пул-реквестом это не решить. Не уверен, что на такие изменения автор пойдёт.

Нет никакого «Serilog для .NET Core»

Согласен. Но Я и не писал, что он есть. Я писал про дизайн Serilog с точки зрения разработчика .NET Core.
Я уже писал в статье, что там нужны слишком концептуальные изменения. Одним только пул-реквестом это не решить. Не уверен, что на такие изменения автор пойдёт.

Значит, ваше утверждение про "приложить больше желания" неверно.


Я писал про дизайн Serilog с точки зрения разработчика .NET Core.

А зачем вы смотрите на пакет, который под .NET Standard и Framework, с точки зрения Core?

А зачем вы смотрите на пакет, который под .NET Standard и Framework, с точки зрения Core?

Затем, что иначе этот функционал недосутпен. А пуш пропертей — это, на мой взгляд, категорически необходимая вещь. Иначе какой толк от Serilog?
Затем, что иначе этот функционал недосутпен.

Какая конкретно функциональность недоступна, если вы будете использовать Serilog напрямую как .NET Standard-пакет?


А пуш пропертей — это, на мой взгляд, категорически необходимая вещь.

"Пуш пропертей", как я уже писал, прекрасно доступен без серилога.


А если вам нужна какая-то функциональность, которая недоступна в Microsoft.Extensions.Logging, то перестаньте требовать, чтобы библиотека, которая вам ее предоставляет, играла по правилам Microsoft.Extensions.Logging. Это взаимопротиворечивые требования.

А можете прояснить для тех, кто в танке…
В чём преимущества использования Serilog по сравнению с NLog?

Когда я последний раз на него смотрел (давно, честно признаюсь), NLog не умел структурное логирование.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации