Использование Asp.Net Core 2 Injection для Serilog с несколькими проектами

У меня настроен Serilog для Asp.Net Core 2.0, и он отлично работает через внедрение зависимостей .Net Core в моем стартовом веб-проекте (если я использую его через Microsoft.Extensions.Logging), но я не могу получить к нему доступ из любого другого проекта.

Вот что у меня есть:

Program.cs

using System;
using System.IO;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Serilog;

namespace ObApp.Web.Mvc
{
    public class Program
    {
        public static IConfiguration Configuration { get; } = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
            .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
            .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true)
            .Build();

        public static void Main(string[] args)
        {
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(Configuration)
                .CreateLogger();

            try
            {
                Log.Warning("Starting BuildWebHost");

                BuildWebHost(args).Run();
            }
            catch (Exception ex)
            {
                Log.Fatal(ex, "Host terminated unexpectedly");
            }
            finally
            {
                Log.CloseAndFlush();
            }
        }

        public static IWebHost BuildWebHost(string[] args) =>
            WebHost.CreateDefaultBuilder(args)
                .UseStartup<Startup>()
                .UseSerilog()
                .Build();
    }
}

Startup.cs

Запуск .cs по умолчанию. Я никоим образом не изменил его по сравнению с оригинальным шаблоном для нового проекта Asp.Net Core MVC.

Я подумал, что мне может понадобиться добавить Serilog в сервисы IServiceCollection, но статья Leaner, подлая регистрация в ASP.NET Core 2 по адресу https://nblumhardt.com/2017/08/use-serilog/, говорит мне, что в этом нет необходимости.

Затем вы можете удалить любую другую конфигурацию регистратора, которая висит вокруг: нет необходимости в разделе "Ведение журнала" в appsettings.json, нигде нет AddLogging() и нет конфигурации через ILoggerFactory в Startup.cs.

UseSerilog() заменяет встроенный ILoggerFactory, так что каждый регистратор в вашем приложении, будь то класс Serilogs Log, Serilogs ILogger или Microsoft.Extensions.Logging.ILogger, будет поддерживаться одной и той же реализацией Serilog и управляться через одну и ту же конфигурацию,

Что я ожидал

Возможность просто внедрить регистратор через конструктор в любом классе в любом проекте в решении. Например:

using Serilog;
public MyTestClass(ILogger logger) { ... }

What я Got - Веб (Startup) проект

Инжекция работает в HomeController, если я использую оболочку Microsoft.Extensions.Logging:

using Microsoft.Extensions.Logging;

public class HomeController : Controller
{
    ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
        _logger.LogDebug("Controller instantiated.");
    }
}

Внедрение не выполняется в любом проекте/классе, если я пытаюсь внедрить Serilog.ILogger

using Serilog;
using Serilog.Extensions.Logging; // Not sure if this would help.

public class HomeController : Controller
{
    ILogger _logger;

    public HomeController(ILogger logger)
    {
        _logger = logger;
        _logger.Debug("Controller instantiated.");
    }
}

InvalidOperationException: невозможно разрешить службу для типа "Serilog.ILogger" при попытке активировать "ObApp2.Web.Mvc.Controllers.HomeController".

Моя большая проблема - другие проекты

Моя большая проблема заключается в том, что я не могу получить регистратор через DI с помощью любого метода, который я пробовал, если я работаю в другом проекте в решении.

Когда я пытаюсь внедрить либо используя Microsoft.Extensions.Logging или Serilog, я получаю исключение отсутствующего параметра во время сборки.

using Microsoft.Extensions.Logging;
namespace ObApp.Domain
{
    public class MyTestClass(ILogger<MyTestClass> logger) { ... }

- or -

using Serilog;
namespace ObApp.Domain
{
    public class MyTestClass(ILogger logger) { ... }

Оба генерируют ошибку сборки, похожую на:

Отсутствует аргумент, который соответствует обязательному формальному параметру 'logger' в MyTestClass.MyTestClass(ILogger).

Вопросы

  1. При внедрении с этим типом конфигурации Serilog рекомендуется ли ссылаться на Microsoft.Extensions.Logging или Serilog в файлах классов, где я делаю инъекцию?

  2. Как я могу заставить DI работать через все проекты?

Ответ 1

Один метод, который работал у меня:

Я добавил экземпляр Serilog.Core.Logger, используя метод AddSingleton() в методе ConfigureServices. Это решило проблему DI. Этот шаг заменяет шаг назначения экземпляра Logger в Log.Logger в конструкторе StartUp.

services.AddSingleton((ILogger)new LoggerConfiguration()
            .MinimumLevel.Information()
            .WriteTo.File(<...>)
            .CreateLogger());

Также измените ссылки в ваших файлах классов, чтобы указать на Serilog.Ilogger

Ответ 2

Это решение корректно внедряет регистратор в классы во внешнем проекте или библиотеке.

Я также попробовал ответ, предложенный Руфусом. У меня не было проблемы, описанной OP, но у меня была еще более странная проблема: после того, как ILogger был добавлен во внешний проект, запись в MS SQL перестала работать. Работа велась в консоли. Поскольку Serilog является главным образом статическим сервисом, я понял, что я должен рассматривать Log.Logger как factory. Преимущество в том, что вы все равно можете настроить ссылки ILogger (например, добавив ForContext в конструктор), не затрагивая службу "singleton".

Я опубликовал полное рабочее решение github, включая консольную программу, которая устанавливает DI (будет работать так же от ASP.NET Core), внешнюю библиотеку, содержащую демоверсию demil-by-zero Serilog, и другую библиотеку, которая управляет Serilog как услугой.

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

using Microsoft.Extensions.DependencyInjection;
using System;

namespace Serilog.Injection
{
    public static class RegisterSerilogServices
    {
        public static IServiceCollection AddSerilogServices(this IServiceCollection services)
        {
            Log.Logger = new LoggerConfiguration()
                .MinimumLevel.Verbose()
                .WriteTo.Console()
                .WriteTo.MSSqlServer(@"xxxxxxxxxxxxx", "Logs")
                .CreateLogger();

            AppDomain.CurrentDomain.ProcessExit += (s, e) => Log.CloseAndFlush();

            return services.AddSingleton(Log.Logger);
        }
    }
}

Ответ 3

Вы не получаете доступ к Serilog через DI в Net Core. Это статика. Правильно настроив и инициализировав Logger в вашей Program.Main(), просто получите доступ, как и к любой другой статической записи в вашем Startup, Controller и т.д., Т.е.

public void ConfigureServices(IServiceCollection services)
            {
    Serilog.Log.Information("here I am in startup");
    //further options
};

или добавьте вверху:

using static Serilog.Log;

и просто:

Information("keystroke saving");

Ответ 4

Я действительно боролся с той же самой установкой, но мне удалось заставить это работать. Решение лишь немного отличается от примеров кода следующим образом:

  • Это зависит от Microsoft.Extensions.Logging.ILogger.
  • Logger вводится с использованием ILogger<Type>

Код вашего контроллера уже соответствует этим требованиям, поэтому это сработало.

using Microsoft.Extensions.Logging;

public class HomeController : Controller
{
    ILogger _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
        _logger.LogInformation("Controller instantiated");
    }
}

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

using Microsoft.Extensions.Logging;

public class MyBusinessObject
{
    ILogger _logger;

    public MyBusinessObject(ILogger<MyBusinessObject> logger)
    {
        _logger = logger;
    }

    public void DoBusinessLog()
    {
        _logger.Information("Executing Business Logic");
    }
}

По какой-то причине DI удается создать экземпляр экземпляра Logger только тогда, когда требуется ILogger<Type> но не для ILogger. Почему это точно, я не знаю, может быть, кто-то другой может ответить на этот вопрос.