Использование 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).
Вопросы
-
При внедрении с этим типом конфигурации Serilog рекомендуется ли ссылаться на Microsoft.Extensions.Logging или Serilog в файлах классов, где я делаю инъекцию?
-
Как я могу заставить 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
. Почему это точно, я не знаю, может быть, кто-то другой может ответить на этот вопрос.