.Net core log Startup errors to app insights - asp.net-core

I'm trying to log any startup errors but the logs are not flowing into application insights. I tried configuring app insights in program.cs but still I don't see any logs.
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
logging.ClearProviders();
logging.AddApplicationInsights();
logging.SetMinimumLevel(LogLevel.Information);
});

To Capture logs from program.cs or startup.cs, you need to configure like this.
.ConfigureLogging((context, builder) =>
{
// Providing a connection string is required if need to capture logs during application startup, such as
// in Program.cs or Startup.cs itself.
builder.AddApplicationInsights(
configureTelemetryConfiguration: (config) => config.ConnectionString = context.Configuration["APPLICATIONINSIGHTS_CONNECTION_STRING"],
configureApplicationInsightsLoggerOptions: (options) => { }
);
// Capture all log-level entries from Program
builder.AddFilter<ApplicationInsightsLoggerProvider>(
typeof(Program).FullName, LogLevel.Trace);
// Capture all log-level entries from Startup
builder.AddFilter<ApplicationInsightsLoggerProvider>(
typeof(Startup).FullName, LogLevel.Trace);
});

For those using .NET 6 projects with minimal hosting model, this worked for me to capture the startup/shutdown messages into Application Insights:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddApplicationInsightsTelemetry();
builder.Logging.AddFilter<ApplicationInsightsLoggerProvider>("Microsoft.Hosting.Lifetime", LogLevel.Information);
...

By default it is not supported to handle exceptions from application startup, see this link.
What I did to get the logs into the application insights is to add a try/catch block in the Main of Program.cs and initialize the TelemetryClient manually.
See the following code:
public static void Main(string[] args)
{
try
{
CreateHostBuilder(args)
.Build().Run();
}
catch(Exception ex)
{
TelemetryConfiguration telemetryConfiguration = TelemetryConfiguration.CreateDefault();
telemetryConfiguration.ConnectionString = "application-insights-connection-string";
var telemetryClient = new TelemetryClient(telemetryConfiguration);
telemetryClient.TrackException(ex);
telemetryClient.Flush();
throw;
}
}

Related

ASP.NET core start dependency injection at when app is started

I am injecting one of my services as last item in the ConfigureServices method:
services.AddSingleton<IBot>(_ => new De.Impl.Bot(Configuration));
I am running the app in docker container so whenever container is restarted I need to invoke my controller so I can get my service running. How can I get it running in the beginning of the configuration part?
As for .NET Core 3.1 and .NET 5.0
First, you can write your business code as an extension:
using Microsoft.Extensions.Hosting;
public static IHost DoSomething(this IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
var yourService = services.GetService<YourService>();
yourService.DoSomething();
}
return host;
}
So if you want to invoke that method every time your application starts, simply call:
// .NET 5.0 Style
public static void Main(string[] args)
{
CreateHostBuilder(args)
.Build()
.DoSomething()
.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args)
{
return Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder => webBuilder.UseStartup<Startup>());
}
As for .NET 6.0
It's gonna be easier. Simply access var yourService = app.Services.GetRequiredService<YourService> in the Program.cs.
// .NET 6.0 style, in Program.cs, before app.Run();
var yourService = app.Services.GetRequiredService<YourService>();
yourService.DoSomething();
app.Run();

Logging in ASP.NET Core Main

I've an ASP.NET Core 3 WebAPI with a simple Main and a CreateHostBuilder.
public static void Main(String[] args)
{
CreateHostBuilder(args).Build().Run();
}
Later logging in my controllers etc. works fine.
But how can I log possible errors in the Main?
You can get get the logger using
// Build the host and configurations
var host = CreateWebHostBuilder(args).Build();
using (var scope = host.Services.CreateScope())
{
// Get the registered logger service
var logger = scope.ServiceProvider.GetRequiredService<ILogger<YourClassName>>();
logger.LogInformation("Logger test");
}
// finally run the host
host.Run();
Another way.....the advantage to this one is this is a separate logger from the HostBuilder's, so it can log things before the Host is even built. It also can be used throughout the class and outside Main in Program.cs. The disadvantage is it cant use the appsettings.json file to configure it (unless someone can show me how).
public class Program
{
// The Program console apps private logger
private static ILogger _logger;
public static void Main(string[] args)
{
// Now create a logging object for the Program class.
ILoggerFactory loggerFactory = LoggerFactory.Create(builder => builder
.AddConsole()
.AddDebug()
);
_logger = loggerFactory.CreateLogger<Program>();
_logger.LogInformation("PROGRAM CLASS >>> The Program Console Class has started...");
}
}
For Net 6.0, this gist works for me https://gist.github.com/filippovd/edc28b511ef0d7dae9ae8f6eb54c30ed
var builder = WebApplication.CreateBuilder(args);
var logger = LoggerFactory
.Create(loggingBuilder =>
{
// Copy all the current providers that was set within WebApplicationBuilder
foreach (var serviceDescriptor in builder.Logging.Services)
{
loggingBuilder.Services
.Add(serviceDescriptor);
}
})
.CreateLogger<Program>();
;
// Add services to the container.
logger.LogInformation("Add services to the container...");
// Console
// ....
info: Program[0]
Add services to the container...

how to handle exception for IHostBuilder CreateHostBuilder

To simulate the error, I gave the wrong azure key vault address. With the below code; I tried all the possible ways to try/catch the exception, but still I get an error when the app is start.
How do I handle this exception so the application does NOT throw the error during startup?
I have ASP.NET Core 3.1 web API application.
HTTP Error 500.30 - ANCM In-Process Start Failure
The actual reason for the error is that I put wrong key vault address,
System.Net.Http.HttpRequestException: 'No such host is known.'
public class Program
{
public static void Main(string[] args)
{
try
{
CreateHostBuilder(args).Build().Run();
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
try
{
var keyVaultClient = KeyVaultClient();
if (keyVaultClient != null)
config.AddAzureKeyVault("https://testkeyvault07021.vault.azure.net", keyVaultClient,
new DefaultKeyVaultSecretManager());
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
})
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
private static KeyVaultClient KeyVaultClient()
{
try
{
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var keyVaultClient =
new KeyVaultClient(
new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
return keyVaultClient;
}
catch (Exception exception)
{
Console.WriteLine(exception);
return null;
}
}
}
The application is actually working just fine, I don't think there is any exact way to solve this situation.
When you start the application, it's the work of the program class to configure the hosting environment, that includes setting up the server, before calling the Startup class to finish the configuration of the application.
Startup class is responsible for creating the pipeline that handles HTTP request. Which means if any error occurs before the Startup class is configured, the server won't know what do with the error or how to handle the error and hence you get the HTTP 500,
If the error had to be handled after the Startup class has been called, and the HTTP pipeline configured with the Configure method, and you had included the
app.UseDeveloperExceptionPage();
Then the correct error message would have been printed back.
The error is generated because you make an HTTP request to the API when building it

Asp Net Core SQL Custom Configuration Provider with Dapper and Error Handling

I am setting up my MVC web application to pull configuration data from my SQL Azure database on startup. I have used these two articles (Microsoft, Medium) to guide me but neither include error handling and I want to avoid any Entity Framework references as i'm using Dapper. So far I've got it working with below code but I'm not sure how to handle errors in this scenario. For instance if I remove the try/catch from the Load method in SQLConfigurationProvider then the app crashes on startup but if I include the try/catch then the error is handled and the app starts normally but no config data is available so will eventually break when trying to access a config value. What is the best way to handle these errors gracefully (ie app still loads but displays an error page/message instead)? Also is there any benefit to having the SQLConfigurationSource or would it make more sense just to create the new SqlConnection instance inside SQLConfigurationProvider instead?
Program.cs
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.CaptureStartupErrors(true)
.UseSetting(WebHostDefaults.DetailedErrorsKey, "true")
.UseApplicationInsights()
.ConfigureAppConfiguration((hostingContext, config) =>
{
config.AddSQLConfiguration(); // Custom configuration here
})
.UseStartup<Startup>();
}
ConfigurationExtensions.cs
public static class ConfigurationExtensions
{
public static IConfigurationBuilder AddSQLConfiguration(this IConfigurationBuilder builder)
{
var connectionString = builder.Build().GetConnectionString("DefaultConnection");
return builder.Add(new SQLConfigurationSource(connectionString));
}
}
SQLConfigurationSource.cs
public class SQLConfigurationSource : IConfigurationSource
{
private readonly SqlConnection _connection;
public SQLConfigurationSource(string connectionString)
{
_connection = new SqlConnection(connectionString);
}
public IConfigurationProvider Build(IConfigurationBuilder builder)
{
return new SQLConfigurationProvider(_connection);
}
}
SQLConfigurationProvider.cs
public class SQLConfigurationProvider : ConfigurationProvider
{
private readonly SqlConnection _connection;
public SQLConfigurationProvider(SqlConnection connection)
{
_connection = connection;
}
public override void Load()
{
try
{
var model = _connection.Query<SQLConfigurationModel>("sp does not exist for example", commandType: CommandType.StoredProcedure);
Data = model.ToDictionary(x => x.Property, x => x.Value);
}
catch (Exception ex)
{
// WHAT TO DO HERE?
}
}
}
public class SQLConfigurationModel
{
public string Property { get; set; }
public string Value { get; set; }
}
---- UPDATE: CLOSE BUT NOT QUITE THERE ----
I added the exception as a configuration value which I then check for in the Configure method of Startup.cs as per below. This helps ensure the app doesn't crash on startup but when I throw the exception it is not getting routed to the Error view even though the exception handler has already been configured with app.UseExceptionHandler("/Home/Error")
// Inside SQLConfigurationProvider
public override void Load()
{
try
{
var model = _connection.Query<SQLConfigurationModel>("sp does not exist for example", commandType: CommandType.StoredProcedure);
Data = model.ToDictionary(x => x.Property, x => x.Value);
}
catch (Exception ex)
{
Data.Add("ConfigurationLoadException", ex.Message);
}
}
// Inside Startup.cs
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseExceptionHandler("/Home/Error");
// Check for custom config exception
string configurationLoadException = Configuration["ConfigurationLoadException"];
if (configurationLoadException.Length > 0)
{
throw new Exception("Configuration Failed: " + configurationLoadException);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
If your application can't work without the configurations stored in SQL, you should move this code to fetch data to have better error management. That way you will be able to show a proper error message to user and log it better. Other option is use try/catch block in program.cs, and the assumption is that the not having the SQL driven configuration, will not break the startup project but further in the application usage. If that's the case, you will already have error management placed in startup and it can show you a functional error page for this.
This link will give you some views about startup/program.cs error handling
You should configure a custom error handling page Please read following. it's easy to do
Custom Error Page .net Core

AspNet Core Logging working but not inside ServiceStack services when hosted in Azure

I have a simple ServiceStack service with some logging added.
log.Info("In Vehicle service request");
if (log.IsDebugEnabled)
log.Debug("Debugging Vehicle service request");
log is defined in a base class as follows;
public abstract class ServiceBase : Service
{
public static readonly ILog log = LogManager.GetLogger(typeof(ServiceBase));
}
The web host is configured to add various logging providers, including log4net (NOTE: I have tried others = same problem).
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((context, config) =>
{
config.SetBasePath(Directory.GetCurrentDirectory());
config
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
.AddJsonFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables(); //lets Azure portal override settings
context.Configuration = config.Build();
})
.ConfigureLogging((hostingContext, logging) =>
{
logging.ClearProviders();
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
logging.AddConsole();
logging.AddDebug();
logging.AddEventSourceLogger();
logging.AddAzureWebAppDiagnostics();
// The ILoggingBuilder minimum level determines the
// the lowest possible level for logging. The log4net
// level then sets the level that we actually log at.
logging.AddLog4Net();
//logging.SetMinimumLevel(LogLevel.Debug);
})
.UseAzureAppServices()
.UseStartup<Startup>();
The ServiceStack AppHost sets the LogFactory early as follows;
public override void Configure(Container container)
{
//Also runs log4net.Config.XmlConfigurator.Configure()
LogManager.LogFactory = new Log4NetFactory(configureLog4Net: true);
..etc
What happens?
I get lovely logging if I add some in my StartUp. However the logging in the ServiceStack service does not appear when hosted in Azure. I do get logging when running locally.
So NetCore is logging ok, but anything in the Service class is not!
Why no logging with this?
public async Task<GetMyDataResponse> Any(GetMyData request)
{
log.Info("In service request");
if (log.IsDebugEnabled)
log.Debug("Debugging service request");
//Some request validation logic could/should go here.
return new GetMyDataResponse
{
Results = await _myDataRepo.FetchAsync()
};
}
In the end it was a silly routing issue, matching to a method in a Controller instead of falling into the ServiceStack route as defined on the interface model. A method I'd left hanging around when testing.
Try either configuring the LogFactory before ServiceStack isinitialized, e.g:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
LogManager.LogFactory = new Log4NetFactory(configureLog4Net: true);
app.UseServiceStack(new AppHost
{
AppSettings = new NetCoreAppSettings(Configuration)
});
}
Or use an instance logger in your Services:
public readonly ILog log = LogManager.GetLogger(typeof(ServiceBase));