Microsoft.Extensions.Logging - Add multiple windows event logs - c#

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

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.

Orleans When Use RedisGrainStorage for "PubSubStore" throw GrainReferenceNotBoundException

Orleans Blazor Server Sample,it work well.
https://github.com/dotnet/orleans/tree/main/samples/Blazor/BlazorServer
I just use Redis Persistence replace memory storage
<PackageReference Include="Orleans.Persistence.Redis" Version="3.1.1" />
await Host.CreateDefaultBuilder(args)
.UseOrleans(builder =>
{
builder.UseLocalhostClustering();
//builder.AddMemoryGrainStorageAsDefault();
builder.AddRedisGrainStorageAsDefault((options) =>
{
options.ConnectionString = redisConnection;
options.UseJson = true;
options.DatabaseNumber = 1;
});
builder.AddSimpleMessageStreamProvider("SMS");
//builder.AddMemoryGrainStorage("PubSubStore");
builder.AddRedisGrainStorage("PubSubStore", options =>
{
options.ConnectionString = redisConnection;
options.UseJson = true;
options.DatabaseNumber = 2;
});
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
})
.RunConsoleAsync();
It work well when first build and run. Redis database created datas.
But when runing again it thrown exception when calling:
public Task<StreamSubscriptionHandle<TodoNotification>> SubscribeAsync(Guid ownerKey, Func<TodoNotification, Task> action) =>
client.GetStreamProvider("SMS")
.GetStream<TodoNotification>(ownerKey, nameof(ITodoGrain))
.SubscribeAsync(new TodoItemObserver(logger, action));
the exception is
GrainReferenceNotBoundException: Attempted to use a GrainReference which has not been bound to the runtime: GrainReference:*grn/BlazorServer.TodoGrain/4794564e-4c87-4674-8722-f87f20559abe-0x3CAFC40C. Use the IGrainFactory.BindGrainReference method to bind this reference to the runtime.
my question is how could I fix this exception? Thanks.

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

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.

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

EasyNetQ - Consuming message from IAdvanceBus by non generic message

Yes we can use auto subscriber functionality and normal subscribe method with this subscription id but this solution is bit ugly in RMQ queues and exchanges. It was difficult to follow and analyze the messages.
I use the advance bus and created my own exchanges and queues. I published successfully but the consuming part is bit disappointment. Currently, it uses this way:
IAdvanceBus = bus.Advanced.Consume(queueName, registration =>
{
registration.Add<MESSAGE1>((message, info) => { ProcessMessage(MESSAGE1) })
registration.Add<MESSAGE2>((message, info) => { ProcessMessage(MESSAGE2) })
registration.Add<MESSAGE3>((message, info) => { ProcessMessage(MESSAGE3) })
registration.Add<MESSAGE4>((message, info) => { ProcessMessage(MESSAGE4) })
registration.Add<MESSAGE5>((message, info) => { ProcessMessage(MESSAGE5) });
});
This is good but the problem if you have hundred listeners?
I checked the registration type IHandlerRegistration it uses only generic one, can we have the none generic way?
Like :
IAdvanceBus = bus.Advanced.Consume(queueName, registration =>
{
registration.Add(typeof(MESSAGE1), info => { ProcessMessage(MESSAGE1) })
registration.Add(typeof(MESSAGE2), info => { ProcessMessage(MESSAGE2) })
registration.Add(typeof(MESSAGE3), info => { ProcessMessage(MESSAGE3) })
});
In this way we can scan the assembly who uses this messages.
In other side, I register via construction of bus:
RabbitHutch.CreateBus(connectionString, registeredServices => {
IEasyNetQLogger logger;
MyCustomHandlerCollection myHandlers = myDIContainer.Resolve<IMyHandlers>();
registeredServices.Register<IHandlerCollection>(s =>
{
logger = s.Resolve<IEasyNetQLogger>();
return myHandlers;
});
registeredServices.Register<IHandlerRegistration>(s => myHandlers});});
But it not respecting my regisration because when I see the code from advance bus consuming:consume code it creates from factory and NOT reading from container. I believe this is a root cause.
To workaround this requirement, I used this method from IAdvanceBus :
IDisposable Consume(IQueue queue, Func<byte[], MessageProperties, MessageReceivedInfo, Task> onMessage);
I rolled my own message dispatcher and deserialize any message from the queue. The collection dispatcher will determine the message type and dispatch to specific handler created using reflection.

Categories