Why is IOptionsMonitor<T>.OnChange not being called? - c#

I'd like to have my .Net Core 3.1 app automatically reload its configuration as the file changes on disk, but I'm having trouble getting this to work with the Options pattern. I run my app, save changes to the config file, and it's never called. Why doesn't the IOptionsMonitor instance ever call the OnChange handler? What am I missing?
Program.cs IHostBuilder creation
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration(config => configuration = config.Build())
.ConfigureServices((hostContext, services) => {
var separateConfig = new ConfigurationBuilder()
.AddJsonFile("SeparateConfig.json", optional: false, reloadOnChange: true)
.Build();
services
.AddSingleton<MyMainService>()
.Configure<MySeparateConfig>(c => separateConfig.Bind(c));
})
Service that uses MySeparateConfig
public class MyMainService
{
public MyMainService(IOptionsMonitor<MySeparateConfig> config)
{
if (config is null) throw new ArgumentNullException(nameof(config));
ConfigChangeHandle = config.OnChange(UpdateConfiguration);
// Perform initial configuration using config.CurrentValue here
}
private IDisposable ConfigChangeHandle { get; set; }
private void UpdateConfiguration(MySeparateConfig config)
{
// Never called
}
}

As #Nkosi pointed out in comments, this line was the problem:
// Wrong
.Configure<MySeparateConfig>(c => separateConfig.Bind(c));
When I replaced it with the line below, everything started working right:
// Right
.Configure<MySeparateConfig>(separateConfig);

For the reload mechanism to work with IOptionsMonitor<TOptions>, an IOptionsChangeTokenSource<TOptions> needs to be registered in the services.
If you need an OptionsBuilder<TOptions>, for example to add validation, this can be achieved with AddOptions + BindConfiguration (from the Microsoft.Extensions.Options.ConfigurationExtensions NuGet package if you don't have a dependency in ASP.NET Core)
services.AddOptions<MyConfig>().BindConfiguration("").Validate(…)
Note that the BindConfiguration extensions method automatically registers the IOptionsChangeTokenSource<TOptions> which is the magic required for the reload mechanism to work.

Related

serilog: how to write logs to 2 different sinks using ASPNETCORE_ENVIRONMENT value

I am using .Net 6 Web Api application using C# and wanted to write logs only to console if ASPNETCORE_ENVIRONMENT = Development and for non-dev wanted to write logs to Azure Blob Storage.
Question 1. How to use app builder before it's created for code if (app.Environment.IsDevelopment()) ? or I have to use Environment.GetEnvironmentVariable?
Question 2. can this be achieved without if/else block and within single line I can change the write context?
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
if (app.Environment.IsDevelopment())
{
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.Console());
}
else
{
builder.Host.UseSerilog((ctx, lc) => lc
.WriteTo.AzureBlobStorage());
}
As answered here you can access the environment from the builder itself:
if (builder.Environment.IsDevelopment())
{
// ...
}
As for "clever" one-liners not much can be done here, I recommend to stick to the if-else as more readable approach (and potentially moving the Serilog setup logic to extension method), but just for funsies - abusing ternary with the discard:
_ = builder.Environment.IsDevelopment()
? builder.Host.UseSerilog((ctx, lc) => lc.WriteTo.Console())
: builder.Host.UseSerilog((ctx, lc) => lc.WriteTo.AzureBlobStorage());
Or
builder.Host.UseSerilog((ctx, lc) => _ = builder.Environment.IsDevelopment()
? lc.WriteTo.Console()
: lc.WriteTo.AzureBlobStorage());
But I hugely recommend considering the ability to set up the logger via configuration.

Microsoft.Extensions.Logging - Add multiple windows event logs

I have a .NET Core application, writing to Windows event viewer.
I'd like to map some of the logs to one source and some to other (based on the caller class).
The setup in Program.cs looks like that:
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging((hostingContext, logging) =>
{
logging.ClearProviders();
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddEventLog(new EventLogSettings()
{
LogName = "CustomerSearchScheduler",
SourceName = "CustomerSearchScheduler",
Filter = (source, level) => source.Contains("Schedulers")
});
logging.AddEventLog(new EventLogSettings()
{
LogName = "CustomerSearch",
SourceName = "CustomerSearch",
Filter = (source, level) => source.Contains("CustomerSearch") && !source.Contains("Schedulers")
});
logging.AddConsole();
})
//Web Host defaults.........
}
Problem is, AddEventLogs seem to override one another. With the code above, nothing is printed to CustomerSearch and only CustomerSearchScheduler shows new logs. When I remove the CustomerSearchScheduler part, the second type works as expected.
How can I make the two work simultaneously?
Thanks
You'll need to explicitly add the event log provider to the service collection:
logging.Services.AddSingleton<ILoggerProvider>(new EventLogLoggerProvider(settings));
The default will not add the service provider twice

Logging in .Net core console application not working

I am following this tutorial: https://andrewlock.net/using-dependency-injection-in-a-net-core-console-application/
and accordingly installed the packages but log is not getting printed anywhere.
This is my code:
var serviceProvider = new ServiceCollection()
.AddLogging()
.AddTransient<IFoo, Foo>(s =>
{
return new Foo()})
.BuildServiceProvider();
//configure console logging
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug);
var logger = serviceProvider.GetService<ILoggerFactory>().CreateLogger<Program>();
logger.LogError("Starting application");
Turns out the console logging provider doesn't immediately flush the messages to the console like it did in the net-core-1.x versions. It appears to run on a different thread. See this web page for info: https://github.com/aspnet/Logging/issues/631
You can add at the end of the Main function.
serviceProvider.Dispose();
or you can add .AddDebug()
serviceProvider
.GetService<ILoggerFactory>()
.AddConsole(LogLevel.Debug)
.AddDebug();
Creating a new ServiceProvider and HostBuilder may not be worth it if we just want a Logging in Console Application because it's a bit of extra caution to clean it up or dispose of.
Rather, I would suggest just have Logging Factory to use logger and that will solve the logging if that is only what we want.
public static class ApplicationLogging
{
public static ILoggerFactory LogFactory { get; } = LoggerFactory.Create(builder =>
builder.ClearProviders();
// Clear Microsoft's default providers (like eventlogs and others)
builder.AddSimpleConsole(options =>
{
options.IncludeScopes = true;
options.SingleLine = true;
options.TimestampFormat = "hh:mm:ss ";
});
builder.AddApplicationInsights("instrument-key");
});
public static ILogger<T> CreateLogger<T>() => LogFactory.CreateLogger<T>();
}
static void Main(string[] args)
{
var logger = ApplicationLogging.CreateLogger<Program>();
logger.LogInformation("Let's do some work");
logger.LogWarning("I am going Crazy now!!!");
logger.LogInformation("Seems like we are finished our work!");
Console.ReadLine();
}
I landed on this thread trying to troubleshoot why console logging didn't work and this answer documents what I found.
Packages used:
Microsoft.Extensions.Logging
Microsoft.Extensions.Logging.Console
Microsoft.Extensions.Logging.Debug
Application:
.NET Core 2.2 Console (Microsoft.NET.Sdk, netcoreapp2.2)
Using Microsoft.Extensions.Hosting.IHost, this is how I added console logging:
var hostBuilder = new HostBuilder()
// Other Configuration omitted for brevity
.ConfigureLogging((hostBuilderContext, loggingBuilder) =>
{
loggingBuilder.AddConfiguration(hostBuilderContext.Configuration.GetSection("Logging"));
loggingBuilder.AddConsole(options =>
{
options.IncludeScopes = true;
});
loggingBuilder.AddDebug();
});
// Start the application
await hostBuilder.RunConsoleAsync();
Interestingly, if I remove the options parameter in the call to AddConsole, I do not see any logging. I believe this is so because I use an ILogger in my code that emits log statements:
public class ClassThatLogs
{
private readonly ILogger<ClassThatLogs> _logger;
public ClassThatLogs(ILogger<ClassThatLogs> logger)
{
_logger = logger;
}
public void DoWork()
{
_logger.LogInformation("Working");
}
}
If I publish the console app as:
single file _log.LogDebug provide no output
when I publish it as not a single file (so I only uncheck the single file option) _log.LogDebug provides outputs on the console all my debugging statements. No other changes than unchecking single file.
So by only unticking "produce single file" my _logging.LogDebug starts to write stuff to the console.
Seems to be this: https://github.com/serilog/serilog-settings-configuration/issues/244

How to configure NServiceBus 6 to use an existing StructureMap container in Web API?

I have a Web API application working with StructureMap and I've introduced the latest version of NServiceBus (version 6) to introduce pub/sub for changed data.
The problem is I don't seem to be able to get the existing container to inject to the StructureMapBuilder.
The structure is as follows:
public class WebApiRegistry : Registry
{
public WebApiRegistry()
{
For(typeof(IRepository<>)).Use(typeof(Repository<>));
For(typeof(IProcessor<>)).Use(typeof(PassThroughProcessor<>));
For<IUnitOfWork>().Use<UnitOfWork>();
For<ILog>().Use(container => LogManager.GetLogger("My.WebApi"));
}
}
This registry is then registered in the Aplication_Start method in the Global.asax:
GlobalConfiguration.Configuration.UseStructureMap<WebApiRegistry>();
The problem is here, in the same method:
var endpointConfiguration = new EndpointConfiguration("My.WebApi.Sender");
endpointConfiguration.UseTransport<MsmqTransport>();
endpointConfiguration.UseSerialization<NServiceBus.JsonSerializer>();
endpointConfiguration.UsePersistence<InMemoryPersistence>();
endpointConfiguration.UseContainer<StructureMapBuilder>(); //No idea how to get the existing container in here???
endpointConfiguration.SendOnly();
I cannot figure out how to register the existing container with NServiceBus.
I've got an example working with AutoFac which is probably because it's the default DI framework of choice for NServiceBus, but I'm keen to get it working with StructureMap.
Any ideas?
Create the container manually and use it for both Web API and NServiceBus
var registry = new WebApiRegistry();
var container = new Container(registry);
//Register StructureMap with GlobalConfiguration
GlobalConfiguration.Configuration.UseStructureMap(container);
var endpointConfiguration = new EndpointConfiguration("My.WebApi.Sender");
//...other code removed for brevity
//Configuring NServiceBus to use the container
endpointConfiguration.UseContainer<StructureMapBuilder>(
customizations: customizations => {
customizations.ExistingContainer(container);
});
//...other code removed for brevity
var endpointInstance = await Endpoint.Start(endpointConfiguration);
// OR Endpoint.Start(endpointConfiguration).GetAwaiter().GetResult();
IMessageSession messageSession = endpointInstance as IMessageSession;
// Now, change the container configuration to know how to resolve IMessageSession
container.Configure(x => x.For<IMessageSession>().Use(messageSession));

Logging from ASP.NET 5 application hosted as Azure Web App

I have an ASP.NET 5 Web API that I host in Azure as a Web App. I want to log messages from my code using Azure Diagnostics. There are multiple article including Azure docs that suggest that it should be as easy as System.Diagnostics.Trace.WriteLine once enabled. The logs should show up under LogsFiles/Application and in log stream in Azure.
I enabled application logging for the web app:
But the following calls produces no logs:
System.Diagnostics.Trace.TraceError("TEST");
System.Diagnostics.Trace.TraceInformation("TEST");
System.Diagnostics.Trace.TraceWarning("TEST");
System.Diagnostics.Trace.WriteLine("TEST");
I tried to manually define TRACE symbol, but with no luck:
I also tried to use the new Microsoft.Extensions.Logging framework and use ILogger.Log API, but without any results again:
public void Configure(IApplicationBuilder app,
IHostingEnvironment env,
ILoggerFactory loggerFactory)
{
loggerFactory.MinimumLevel = LogLevel.Debug;
var sourceSwitch = new SourceSwitch("Sandbox.AspNet5.ApiApp-Demo");
sourceSwitch.Level = SourceLevels.All;
loggerFactory.AddTraceSource(sourceSwitch,
new ConsoleTraceListener(false));
loggerFactory.AddTraceSource(sourceSwitch,
new EventLogTraceListener("Application"));
}
Any ideas on what am I doing wrong?
If you look at your web.config, it probably has stdoutLogEnabled="false". If you set that to true, then anything that is written to standard output will be written to a file. And the stdoutLogFile determines where it goes, which by default is under d:\home\logfiles.
Now you need to make sure that your logging actually goes to stdout. Doing Console.WriteLine would definitely work. I think it's probably possible to also configure things via ILoggerFactory in your startup.cs to make logs go to stdout.
I've found an simple trick for azure app ,see https://github.com/aspnet/Logging/tree/dev/src/Microsoft.Extensions.Logging.AzureAppServices,
please add the package "Microsoft.Extensions.Logging.AzureAppServices": "1.0.0-preview1-final" and update related dependencies,
add the azure diagnostics in startup.cs like this :
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
loggerFactory.AddAzureWebAppDiagnostics(); // for default setting.
or for custom setting:
loggerFactory.AddAzureWebAppDiagnostics(new AzureAppServicesDiagnosticsSettings( ...)); // add custom setting.
// see here for detailed member properties: https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.AzureAppServices/AzureAppServicesDiagnosticsSettings.cs
And enable the diagnostics log on azure, both logging on blob and file are work well. No need for any
extra configuration. :)
I got Streaming Log output working for an ASP.NET 5 web app by writing the following class (based on David Ebbo's example):
public class AzureApplicationLogTraceListener : TraceListener
{
private readonly string _logPath;
private readonly object _lock = new object();
public AzureApplicationLogTraceListener()
{
string instanceId = Environment.GetEnvironmentVariable("WEBSITE_INSTANCE_ID");
if (instanceId != null)
{
string logFolder = Environment.ExpandEnvironmentVariables(#"%HOME%\LogFiles\application");
Directory.CreateDirectory(logFolder);
instanceId = instanceId.Substring(0, 6);
_logPath = Path.Combine(logFolder, $"logs_{instanceId}.txt");
}
}
public override void Write(string message)
{
if (_logPath != null)
{
lock (this)
{
File.AppendAllText(_logPath, message);
}
}
}
public override void WriteLine(string message)
{
Write(message + Environment.NewLine);
}
}
and then putting this in my Startup.Configure:
Trace.Listeners.Add(new AzureApplicationLogTraceListener());
This only supports filesystem-based logging (which is sufficient for live log streams).

Categories