how to send logging data to app insights from a library project - asp.net-core

I have an aspnet core 2 web app which relies on a "Business" project to handle some logic. I am trying to set up the web app so that its ILogger logs are sent to App Insights. I can send the logs fine if I call the logger.Log method from within its Controller Actions. However, when making calls to classes in another project, which is part of the same solution, where I have an instance of ILogger and logging from there, it doesn't send any log data to App Insights. Am I missing something here? I would imagine if I have configured the logging and app insights in the web app correctly, I can call any other library from there and the logging data would be sent fine.

Approach 1 -
You will have to register your business logic class in Core API project in Startup; something similar to below
builder.Services.AddScoped<IMyClass, MyClass>();
And you will have to define a constructor which takes an ILogger instance in your business class; something like below
private readonly ILogger<MyClass> _logger;
public MyClass(ILogger<MyClass> logger)
{
_logger = logger;
}
// _logger.LogInformation("Hi from MyClass"); e.g. logging
This approach means ILogger will be injected with all required settings into your business lib.
Approach 2 -
You will have to install the AppInsights worker package in your business lib project
Microsoft.ApplicationInsights.WorkerService
from here
Then, you will have to write code for logging into AppInsights, something like below
var serviceCollection = new ServiceCollection();
serviceCollection.AddApplicationInsightsTelemetryWorkerService(options => options.ConnectionString = "my-key");
serviceCollection.AddLogging(builder => builder
.AddFilter<Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider>("", LogLevel.Information)
.AddFilter("Default", LogLevel.Information)
.AddFilter("Microsoft", LogLevel.Warning)
.AddFilter("System", LogLevel.Warning)
);
var serviceProvider = serviceCollection.BuildServiceProvider();
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
var telemetryClient = serviceProvider.GetService<TelemetryClient>();
var logger = loggerFactory.CreateLogger("my-logger");
logger.LogInformation("Hi from MyClass");
// flush and sleep at the end, before returning to the caller so that no messages are lost
telemetryClient.Flush();
System.Threading.Thread.Sleep(5000);
You would need the Microsoft.Extensions.DependencyInjection and Microsoft.Extensions.Logging packages in your business lib project for this approach. This approach means you will write appinsights logging independently to your lib project.

Related

Register dependent services on every request

I am working in Multi-tenant solution primarily there are 2 type of applications
WebAPI
Console app to process message from queue
I have implemented dependency injection to inject all services. I have crated TenantContext class where I am resolving tenant information from HTTP header and it's working fine for API, but console application getting tenant information with every message (tenant info is part of queue message) so I am calling dependency injection register method on every incoming message which is not correct, do you have any suggestion/solution here?
The way I am resolving ITenantContext in API
services.AddScoped<ITenantContext>(serviceProvider =>
{
//Get Tenant from JWT token
if (string.IsNullOrWhiteSpace(tenantId))
{
//1. Get HttpAccessor and processor settings
var httpContextAccessor =
serviceProvider.GetRequiredService<IHttpContextAccessor>();
//2. Get tenant information (temporary code, we will get token from JWT)
tenantId = httpContextAccessor?.HttpContext?.Request.Headers["tenant"]
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(tenantId))
//throw bad request for api
throw new Exception($"Request header tenant is missing");
}
var tenantSettings =
serviceProvider.GetRequiredService<IOptionsMonitor<TenantSettings>>();
return new TenantContext(tenantId, tenantSettings );
});
Create two different ITenantContext implementations. One for your Web API, and one for your Console application.
Your Web API implementation than might look as follows:
public class WebApiTenantContext : ITenantContext
{
private readonly IHttpContextAccessor accessor;
private readonly IOptionsMonitor<TenantSettings> settings;
public WebApiTenantContext(
IHttpContextAccessor accessor,
IOptionsMonitor<TenantSettings> settings)
{
// Notice how the dependencies are not used in this ctor; this is a best
// practice. For more information about this, see Mark's blog:
// https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
this.accessor = accessor;
this.settings = settings;
}
// This property searches for the header each time its called. If needed,
// it can be optimized by using some caching, e.g. using Lazy<string>.
public string TenantId =>
this.accessor.HttpContext?.Request.Headers["tenant"].FirstOrDefault()
?? throw new Exception($"Request header tenant is missing");
}
Notice that this implementation might be a bit naive for your purposes, but hopefully you'll get the idea.
This class can be registered in the Composition Root of the Web API project as follows:
services.AddScoped<ITenantContext, WebApiTenantContext>();
Because the WebApiTenantContext has all its dependencies defined in the constructor, you can do a simple mapping between the ITenantContext abstraction and the WebApiTenantContext implementation.
For the Console application, however, you need a very different approach. The WebApiTenantContext, as shown above, is currently stateless. It is able to pull in the required data (i.e. TenantId) from its dependencies. This probably won't work for your Console application. In that case, you will likely need to manually wrap the execution of each message from the queue in a IServiceScope and initialize the ConsoleTenantContext at the beginning of that request. In that case, the ConsoleTenantContext would look merely as follows:
public class ConsoleTenantContext : ITentantContext
{
public string TenantId { get; set; }
}
Somewhere in the Console application's Composition Root, you will have to pull messages from the queue (logic that you likely already have), and that's the point where you do something as follows:
var envelope = PullInFromQueue();
using (var scope = this.serviceProvider.CreateScope())
{
// Initialize the tenant context
var context = scope.ServiceProvider.GetRequiredService<ConsoleTenantContext>();
content.TenantId = envelope.TenantId;
// Forward the call to the message handler
var handler = scope.ServiceProvider.GetRequiredService<IMessageHandler>();
handler.Handle(envelope.Message);
}
The Console application's Composition Root will how have the following registrations:
services.AddScoped<ConsoleTenantContext>();
services.AddScoped<ITenentContext>(
c => c.GetRequiredServices<ConsoleTenantContext>());
With the registrations above, you register the ConsoleTenantContext as scoped. This is needed, because the previous message infrastructure needs to pull in ConsoleTenantContext explicitly to configure it. But the rest of the application will depend instead on ITenantContext, which is why it needs to be registered as well. That registration just forwards itself to the registered ConsoleTenantContext to ensure that both registrations lead to the same instance within a single scope. This wouldn't work when there would be two instances.
Note that you could use the same approach for Web API as demonstrated here for the Console application, but in practice it's harder to intervene in the request lifecycle of Web API compared to doing that with your Console application, where you are in full control. That's why using an ITenantContext implementation that is itself responsible of retrieving the right values is in this case an easier solution for a Web API, compared to the ITenantContext that is initialized from the outside.
What you saw here was a demonstration of different composition models that you can use while configuring your application. I wrote extensively about this in my series on DI Composition Models on my blog.

How to inject log instance into net core class Library

I'm working into net core identity Server project. in the StartUp class this code create a log object
public Startup(IConfiguration configuration, ILogger<Startup> logger)
{
StaticConfig = configuration;
_logger = logger;
}
the same code is inside the api project. Both IdentityServer and Api needs to call functions from an old Soap service. So i create a new net core class Library called SoapClient to share the functions between the two projects.
this the code to instantiate soapClient from IS and Api projects:
SoapClient.Client client = new SoapClient.Client(
Startup.StaticConfig.GetValue<string>("soapClient:server")
);
Now i dont know how to inject the log instance inside SoapClient Library. i Tried this into the
constructor but logger is always null:
public Client(string serviceUrl, ILogger<Client> logger = null)
{
_ServiceUrl = serviceUrl;
logger.LogDebug("Created");
}
if remove the default null to logger parameter, the code does compiler because logger is expected to be passed when the costructor is called from IS project and Api project.
You should use dependency injection for this kind of scenarios. The callers should not be responsible of knowing how to create an instance of SoapClient.Client.
Just register your SoapClient.Client into DI. And naturally you can have both the config (via IOptions) and logger injected automatically.
If for whatever reason you don't want to use DI, the caller will have to have an ILoggerFactory injected and you can create ILogger instances using the ILoggerFactory.

How to instantiate a logger implementing Microsoft.Extensions.Logging.ILogger<T> that logs to Serilog logger

How do I instantiate a logger implementing Microsoft.Extensions.Logging.ILogger<out TCategoryName> that will output logs to my Serilog logger, when I cannot do it via standard ASP.NET Core dependency injection?
I have Serilog configured in my ASP.NET Core project, and the standard dependency injection set up is correctly injecting loggers into my controllers and services. The output from these loggers is correctly written to the file as specified in my Serilog configuration.
However, I need to pass a logger to an object that is instantiated during Startup.ConfigureServices(...), so I cannot resolve it via DI. I can instantiate a Serilog.Logger, but since I want to avoid direct dependencies on Serilog outside of my configuration code, I am forced to use my own adaptor. I would expect there to be some existing adaptor that would use Microsoft.Extensions.Logging.ILogger<out TCategoryName>, but I can't work out how to do it.
The precise use case is to inject a logger into a DbCommandInterceptor, which I am trying to do inside Startup.ConfigureServices(...):
var databaseConfiguration = this.Configuration
.GetSection(nameof(DatabaseConfiguration))
.Get<DatabaseConfiguration>();
var interceptor = new LoggingDbCommandInterceptor(
warning => Log.Logger.Warning("{Warning}", warning),
error => Log.Logger.Error("{Error}", error),
databaseConfiguration);
var dbConfiguration = new MyDbConfiguration(interceptor);
DbConfiguration.SetConfiguration(dbConfiguration);
I found the answer, and a bunch of other helpful tips here:
https://blog.rsuter.com/logging-with-ilogger-recommendations-and-best-practices/
var loggerFactory = (ILoggerFactory)new LoggerFactory();
loggerFactory.AddSerilog(serilogLogger);
var logger = loggerFactory.CreateLogger<TCategoryName>();

Shared service provider NServiceBus and ASPNET Core

I'm creating a way to publish integration events via NServiceBus that are published from within an operation executed in a handler. The path I've chosen is bridge the IIntegrationEventProvider with IEventCollectionPublisher to get the published events from domain layer.
public sealed class Bridge : IIntegrationEventProvider /* Infrastructure */,
IEventCollectionPublisher /* Domain */
{
private readonly List<object> _events = new List<object>();
void IEventCollectionPublisher.Publish(object domainEvent) { _events.Add(domainEvent): }
IReadOnlyCollection IIntegrationEventProvider.GetEvents() => _events;
}
Since NServiceBus has its own service provider (IBuilder) I need to resolve the class doing the application operation from the IServiceProvider that is made available to pipeline in ServiceScopedBehavior. Doing this I can get the bridge instance that contains the events published from domain layer and publish them as integration events using NServiceBus.
I published a Gist with (hopefully) the code pieces needed to grasp what I'm trying to achieve.
The question is: can I instruct NServiceBus to just delegate calls to the application service provider instead of building it and copy all instructions in endpoint.UserContainer<ServiceBuilder>()? Below is an example
internal sealed class Handler : IHandleMessages<Command>
{
public async Task Handle(Command message, IMessageHandlerContext context)
{
// Resolved from ASPNET DI
var useCase = context.GetService<CommandUseCase>();
// _useCase is resolved NSB DI since injected from constructor
Debug.Assert(ReferenceEquals(useCase, _useCase), "");
await useCase.Execute().ConfigureAwait(false);
}
}
This way I could inject to correct scoped application class in the handler constructor instead of resolving it from the scope provided by IServiceProvider that is made available from context.Extensions.Get<IServiceScope>().ServiceProvider.
Thanks for help
Regards
I think ASP.NET Core integration sample could be useful. Starting from version 7.2 sharing of the DI infrastructure between ASP.NET and NServiceBus is much simpler. There is also a specialized NServiceBus.Extensions.Hosting adapter package that adds UseNServiceBus API.

Ninject with Log4Net extensions via TaskScheduller no logging output and no error

I have a console application project that is using Ninject and Log4Net.
When i run the app on my machine, the logging is working fine. When i run the app on the production server, the logging is working fine. When i run the program via TaskScheduller task which is being set so that is is being run by some other user, i get no logging output by any of the appenders. I'm using RollingFileAppender, SmtpAppender and AdoNetAppender.
The strange thing is, that the program is running fine, it just doesnt log anything.
I presume that because the app is working if i run it locally, the log4net configuration is fine.
I resolve logger in the main method of the program and then inject it via constructor parameter when needed. This is how i get the logger in the main method of the app:
XmlConfigurator.ConfigureAndWatch(new FileInfo("log4net.config"));
var kernel = new StandardKernel();
var loggerFactory = kernel.Get<Log4NetLoggerFactory>();
ILogger logger = loggerFactory.GetCurrentClassLogger();
logger.Info(" Test ");
Any hints, pointers or anything....as i don't know what else to try.
The extension is normally used like this:
public class MyClass
{
private readonly ILogger log;
public MyClass(ILogger log)
{
this.log = log;
log.Info("Created MyClass");
}
}
XmlConfigurator.ConfigureAndWatch(new FileInfo("log4net.config"));
using (IKernel kernel = new StandardKernel())
{
kernel.Bind<MyClass>().ToSelf();
kernel.Get<MyClass>(); // will cause the log message to print
}
Just let Ninject worry about injecting the ILogger into your class. You can't request an ILogger from the IKernel in the same place you declare the kernel because you've got no context.
You can do this though:
ILogger log = new Log4NetLoggerFactory().GetCurrentClassLogger();
log.Info("Test");