Closed scottmwyant closed 7 months ago
Hi @scottmwyant, thanks for the note. Does builder.Services.AddSerilog()
work as you expect? (I'm happy adding the UseSerilog()
sugar on the higher-level interface - just keen to validate that we got the functionality right in #66). Thanks!
@nblumhardt I will need to review. Apologies for not being clear about the actual problem; perhaps I just need some education. Looking to use Serilog.Sinks.File. Typically I would specify the log file to be the content root. Need access to host context to do that. Is there a different way using the IServiceCollection extension?
Leaving a solution in hopes to save somebody else some time. I just started working with at the new "linear" API for HostApplicationBuilder
, after looking at it for a while, it's perfectly fine as is.
var builder = Host.CreateApplicationBuilder();
builder.Services.AddWindowsService(options => options.ServiceName = ".NET Joke Service");
builder.Logging
.ClearProviders()
.AddSerilog(
new LoggerConfiguration()
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.File(Path.Join(builder.Environment.ContentRootPath, "myApp.log"))
.CreateLogger()
);
IHost host = builder.Build();
Thanks for closing the loop, @scottmwyant.
I think the intended solution via the earlier PR is actually:
var builder = Host.CreateApplicationBuilder();
builder.Services.AddWindowsService(options => options.ServiceName = ".NET Joke Service");
builder.Services.AddSerilog(lc => lc
.MinimumLevel.Debug()
.MinimumLevel.Override("Microsoft", Serilog.Events.LogEventLevel.Information)
.Enrich.FromLogContext()
.WriteTo.File(Path.Join(builder.Environment.ContentRootPath, "myApp.log")));
IHost host = builder.Build();
Does this variation work for you?
@nblumhardt, actually no, I'm not seeing that. I don't have an .AddSerilog()
overload that takes an Action<LoggerConfiguration>
. Serilog.Extensions.Hosting v7.0.0, dotnet runtime 7.0.10.
Thanks - I'll take another look at it 👍
Not to be the proverbial "It works on my machine" but it works on my machine. Was just working out how to add serilog to an IHostApplicationBuilder and found this issue, the comment about builder.Services.AddSerilog
helped as I'd written my own extension method to do the whole builder.Logging.ClearProviders
beforehand.
Working MVP
Project.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<RootNamespace>SerilogTest</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Content Include="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="7.0.1" />
<PackageReference Include="Serilog.Enrichers.Environment" Version="2.2.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Extensions.Hosting" Version="7.0.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="7.0.1" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
</ItemGroup>
</Project>
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace SerilogTest;
internal static class Program
{
static async Task Main(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSerilog(config =>
{
config.ReadFrom.Configuration(builder.Configuration);
});
builder.Services.AddHostedService<App>();
var host = builder.Build();
await host.RunAsync();
}
}
App.cs
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
namespace SerilogTest;
internal class App : IHostedService
{
private readonly ILogger<App> logger;
public App(ILogger<App> logger)
{
this.logger = logger;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Application Started");
await Task.CompletedTask;
}
public async Task StopAsync(CancellationToken cancellationToken)
{
logger.LogInformation("Application Stopped");
await Task.CompletedTask;
}
}
appsettings.json
{
"Serilog": {
"Using": [ ],
"WriteTo": [
{
"Name": "Console",
"Args": {
"formatter": "Serilog.Formatting.Compact.CompactJsonFormatter, Serilog.Formatting.Compact"
}
}
],
"Enrich": [ "FromLogContext", "WithThreadId" ]
}
}
{"@t":"2023-08-31T09:58:52.0739377Z","@mt":"Application Started","SourceContext":"SerilogTest.App","ThreadId":1}
{"@t":"2023-08-31T09:58:52.0901752Z","@mt":"Application started. Press Ctrl+C to shut down.","SourceContext":"Microsoft.Hosting.Lifetime","ThreadId":1}
{"@t":"2023-08-31T09:58:52.0991706Z","@mt":"Hosting environment: {EnvName}","EnvName":"Production","SourceContext":"Microsoft.Hosting.Lifetime","ThreadId":1}
{"@t":"2023-08-31T09:58:52.0996216Z","@mt":"Content root path: {ContentRoot}","ContentRoot":"\\bin\\Debug\\net6.0","SourceContext":"Microsoft.Hosting.Lifetime","ThreadId":1}
{"@t":"2023-08-31T09:58:54.2816410Z","@mt":"Application is shutting down...","SourceContext":"Microsoft.Hosting.Lifetime","ThreadId":9}
{"@t":"2023-08-31T09:58:54.3143022Z","@mt":"Application Stopped","SourceContext":"SerilogTest.App","ThreadId":3}
@gwlg-mjh's solution worked for me! I would still be interested in sugar for a .UseSerilog()
on HostApplicationBuilder
@gwlg-mjh's solution is very clean. I think it would be helpful to update the serilog-extensions-hosting or serilog-settings-configuration README samples to take this approach, since it shows very nicely how to combine both together with the .NET 7/8 hosting APIs, which I was struggling with a bit as a newbie.
need to add Serilog.Settings.Configuration
nuget package for this to work. (for the .Configuration()
extension method). serilog docs kind of are lacking.
For anyone still looking for an extension method to use, here's an example of what the extension method could look like:
public static IHostApplicationBuilder UseSerilog(this IHostApplicationBuilder builder, Action<IHostApplicationBuilder, IServiceProvider, LoggerConfiguration> configureLogger, bool preserveStaticLogger = false, bool writeToProviders = false)
{
if (builder is null)
{
throw new ArgumentNullException(nameof(builder));
}
if (configureLogger is null)
{
throw new ArgumentNullException(nameof(configureLogger));
}
builder.Logging.Services.AddSerilog(
(services, loggerConfiguration) => configureLogger(builder, services, loggerConfiguration),
preserveStaticLogger,
writeToProviders);
return builder;
}
Instead of an HostBuilderContext
being passed into the lambda, it gets replaced with IHostApplicationBuilder
itself.
This is basically @gwlg-mjh 's comment wrapped in an extension method, however it uses builder.Logger.Services
instead, which I believe is more correct for registering logging services.
For anyone still looking for an extension method to use, here's an example of what the extension method could look like:
public static IHostApplicationBuilder UseSerilog(this IHostApplicationBuilder builder, Action<IHostApplicationBuilder, IServiceProvider, LoggerConfiguration> configureLogger, bool preserveStaticLogger = false, bool writeToProviders = false) { if (builder is null) { throw new ArgumentNullException(nameof(builder)); } if (configureLogger is null) { throw new ArgumentNullException(nameof(configureLogger)); } builder.Logging.Services.AddSerilog( (services, loggerConfiguration) => configureLogger(builder, services, loggerConfiguration), preserveStaticLogger, writeToProviders); return builder; }
Instead of an
HostBuilderContext
being passed into the lambda, it gets replaced withIHostApplicationBuilder
itself.This is basically @gwlg-mjh 's comment wrapped in an extension method, however it uses
builder.Logger.Services
instead, which I believe is more correct for registering logging services.
What do you think about clearing the existing logging providers before the AddSerilog call?
i.e.: builder.Logging.ClearProviders();
Reopening because we need some documentation around this. I think we should also look at exposing IConfiguration
through an overload of the AddSerilog()
callback.
Dug in some more, consuming configuration is already as simple as:
builder.Services.AddSerilog(lc => lc
.ReadFrom.Configuration(builder.Configuration));
README updated now - thanks everyone :-)
This package has extension methods for
IHostBuilder
. In .net7, the official documentation for Generic Host encourages use ofHost.CreateApplicationBuilder
which returns aHostApplicationBuilder
. The trouble is thatHostApplicationBuilder
does not implement theIHostBuilder
interface.I propose that, in addition to the current capabilities, the Serilog.Extensions.Hosting package should offer a
.UseSerilog
extension method forIHostApplicationBuilder
.Reference:
Edit: I now see that
IHostApplicationBuilder
is included in .net8. Updating title. Best course of action for .net7 would probaby be to useHost.CreateDefaultBuilder
to get the .net6 style API (using callbacks).