The .NET Core Worker service is yet another tool in the .NET toolbox. They are perfect for background processing like reading from a queue or making health checks. They are cross-platform (of course) and they can run on docker containers, and even run on a locally hosted machine as a Windows service.
It took me quite some time to implement Application Insights and file logging (Serilog) in my worker service, so I thought I might write it down, in case I need it another time.
THE NUGET PACKAGES
First caveat was when I realized that you need a special Microsoft.ApplicationInsights.WorkerService NuGet package before Application Insights will work. Also, if you wish to run the worker service as a Windows service you need the Microsoft.Extensions.Hosting.WindowsServices package.
The packages I needed was:
Microsoft.ApplicationInsights.AspNetCore
Microsoft.ApplicationInsights.WorkerService
Microsoft.Extensions.Hosting
Microsoft.Extensions.Hosting.WindowsServices
Serilog
Serilog.AspNetCore
Serilog.Extensions.Hosting
Serilog.Extensions.Logging
Serilog.Settings.Configuration
Serilog.Sinks.Console
System.Configuration.ConfigurationManager
THE APPSETTINGS.JSON CONFIGURATION
The configuration is pretty straight forward. Only caveat is that Serilog is not configured under “Logging” but under it’s own namespace:
{ "ApplicationInsights": { "InstrumentationKey": "the-appinsights-guid" }, "Serilog": { "WriteTo": [ { "Name": "File", "Args": { "path": "log_.txt", "rollingInterval": "Day" } } ] }, "Logging": { "ApplicationInsights": { "LogLevel": { "Default": "Information", "Microsoft": "Warning" } }, "LogLevel": { "Default": "Information", "Microsoft": "Information", "Microsoft.Hosting.Lifetime": "Warning" } } }
THE PROGRAM.CS
The examples online was too complex for me, so I simplified it and came up with this:
namespace MyWorkerService { public class Program { public static void Main(string[] args) { // This will run the worker service CreateHostBuilder(args).Build().Run(); } public static IHostBuilder CreateHostBuilder(string[] args) { var host = Host.CreateDefaultBuilder(args); // Add this line to be able to run as a windows service // https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/windows-service?view=aspnetcore-3.1&tabs=visual-studio host.UseWindowsService(); // This will add the configuration to the application. // It will allow you to inject the configuration as // IConfiguration configuration in your constructors host.ConfigureAppConfiguration( (hostContext, config) => { config.SetBasePath(Directory.GetCurrentDirectory()); config.AddJsonFile("appsettings.json", false, true); config.AddCommandLine(args); } ); // This will configure logging. It reads the log settings from // the appsettings.json configuration file, and adds serilog, // allowing the application to write logs to file host.ConfigureLogging( loggingBuilder => { var configuration = new ConfigurationBuilder() .AddJsonFile("appsettings.json") .Build(); var logger = new LoggerConfiguration() .ReadFrom.Configuration(configuration) .CreateLogger(); loggingBuilder.AddSerilog(logger, dispose: true); } ); // The AddHostedServer adds the worker service to the application. // The AddApplicationInsightsTelemetryWorkerService is very important. Without this, // the Application Insights logging will not work. host.ConfigureServices((hostContext, services) => { services.AddHostedService<Worker>(); services.AddApplicationInsightsTelemetryWorkerService(); }); return host; } } }
The worker itself is the “Worker” class and it looks like this:
using System.Threading; using System.Threading.Tasks; using Microsoft.ApplicationInsights; using Microsoft.ApplicationInsights.DataContracts; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; namespace MyWorkerService { public class Worker : BackgroundService { private readonly ILogger<Worker> _logger; private readonly TelemetryClient _telemetryClient; public Worker(ILogger<Worker> logger, TelemetryClient telemetryClient, IConfiguration configuration) { _logger = logger; _telemetryClient = telemetryClient; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using (_telemetryClient.StartOperation<RequestTelemetry>("Execute Async")) { _logger.LogInformation("ExecuteAsync Started"); DoWork(); } await Task.Delay(1000, stoppingToken); } } } }
The DoWork() is where you would do actual work.
MORE TO READ:
- Background tasks with hosted services in ASP.NET Core from Microsoft
- Demystifying the new .NET Core 3 Worker Service from Microsoft Premier Developer
- Building a Windows service with Worker Services and .NET Core 3.1, part 1: Introduction by Anthony Giretti
- Serilog.NET
- Microsoft.ApplicationInsights.WorkerService NuGet Package
- Microsoft.Extensions.Hosting.WindowsServices NuGet Package
- Host ASP.NET Core in a Windows Service from Microsoft
Nice post! It has been really useful to me.
An improvement could be reading the configuration settings by using the hostingContext parameter:
.ConfigureLogging((hostingContext, loggingBuilder) =>
{
Logger logger = new LoggerConfiguration().ReadFrom.Configuration(hostingContext.Configuration).CreateLogger();
loggingBuilder.AddSerilog(logger, dispose: true);
})
This solves the issue about having additional configuration files like appsettings.Production.json
LikeLike
Is there a way to have a middleware layer for error logging in a worker service? As a way not to crash the service for any unexpected behavior? I have everything wrapped up in try/catch but somehow the testers find ways to cause an exception and it crashes before logging and they’re too lazy to take a screenshot while testing in console
LikeLike
There are middleware layers for everything else in .net core, but I haven’t found any for worker services. If you do find out how to do that, please let me know :)
LikeLike
I tweeted David Fowler about that and he mentions there’s no middleware layer for worker services, everything should be in code. Web API’s made me lazy man :P
LikeLike
Hi. Nice article. I’m trying to reimplement your solution, but something’s wrong. It gets the correct log.txt, but the logs are not send to application Insights. I only changed the the-appinsights-guid
LikeLike
Pingback: C# Set local folder for .net Core Windows Services | Brian Pedersen's Sitecore and .NET Blog
Pingback: C# Log to Application Insights and File from your .NET 6 Application | Brian Pedersen's Sitecore and .NET Blog