-
.NET Azure Functions, Isolated worker model, Serilog to App Insights
There's already some good resources online about configuring .NET Azure Functions with Serilog. For example, Shazni gives a good introduction to Serilog and then shows how to configure for in-process and isolated Azure Functions, and Simon shows how to use Serilog with Azure Functions in isolated worker model, but neither cover using App Insights.
It's important to note that the in-process model goes out of support (along with .NET 8) in November 2026. Going forward, only the isolated worker model is supported by future versions of .NET (starting with .NET 9)
The Serilog Sink package for logging data to Application Insights is Serilog.Extensions.AppInsights, and it has some useful code samples in the README, but they also lack mentioning the differences for isolated worker model.
So my goal here is to demonstrate the following combination:
- A .NET Azure Function
- That is using isolated worker mode
- That logs to Azure App Insights
- Uses Serilog for structured logging
- Uses the Serilog 'bootstrapper' pattern to capture any errors during startup/configuration
Note: There are full working samples for this post in https://github.com/flcdrg/azure-function-dotnet-isolated-logging.
Our starting point is an Azure Function that has Application Insights enabled. We uncommented to the two lines in Program.cs and the two .csproj file from the default Functions project template.
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; var host = new HostBuilder() .ConfigureFunctionsWebApplication() .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); }) .Build(); host.Run();
One of the challenges with using the App Insights Serilog Sink, is that it needs to be configured with an existing
TelemetryConfiguration
. The old way of doing this was to referenceTelemetryConfiguration.Active
, however using this property has been discouraged in .NET Core (aka modern .NET).Instead you're encouraged to retrieve a valid
TelemetryConfiguration
instance from the service provider, like this:Log.Logger = new LoggerConfiguration() .WriteTo.ApplicationInsights( serviceProvider.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces) .CreateLogger();
Except we have a problem. How can we reference the service provider? We need to move this under the
HostBuilder
, so we have access to a service provider.There's a couple of ways to do this. Traditionally we would use
UseSerilog
to register Serilog similar to this:var build = Host.CreateDefaultBuilder(args) .UseSerilog((_, services, loggerConfiguration) => loggerConfiguration .Enrich.FromLogContext() .Enrich.WithProperty("ExtraInfo", "FuncWithSerilog") .WriteTo.ApplicationInsights( services.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces))
But as of relatively recently, you can now also use
AddSerilog
, as it turns out under the covers,UseSerilog
just callsAddSerilog
.So this is the equivalent:
builder.Services .AddSerilog((serviceProvider, loggerConfiguration) => { loggerConfiguration .Enrich.FromLogContext() .Enrich.WithProperty("ExtraInfo", "FuncWithSerilog") .WriteTo.ApplicationInsights( serviceProvider.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces); })
There's also the 'bootstrap logging' pattern that was first outlined here.
This can be useful if you want to log any configuration errors at start up. The only issue here is it will be tricky to log those into App Insights as you won't have the main Serilog configuration (where you wire up App Insights integration) completed yet. You could log to another sink (Console, or Debug if you're running locally).
Here's an example that includes bootstrap logging.
using Microsoft.Azure.Functions.Worker; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.ApplicationInsights.Extensibility; using Serilog; Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.Debug() .CreateBootstrapLogger(); try { Log.Warning("Starting up.."); // Only logged to console var build = Host.CreateDefaultBuilder(args) .UseSerilog((_, services, loggerConfiguration) => loggerConfiguration .Enrich.FromLogContext() .Enrich.WithProperty("ExtraInfo", "FuncWithSerilog") .WriteTo.ApplicationInsights( services.GetRequiredService<TelemetryConfiguration>(), TelemetryConverter.Traces)) .ConfigureFunctionsWebApplication() .ConfigureServices(services => { services.AddApplicationInsightsTelemetryWorkerService(); services.ConfigureFunctionsApplicationInsights(); }) .ConfigureLogging(logging => { // Remove the default Application Insights logger provider so that Information logs are sent // https://learn.microsoft.com/en-us/azure/azure-functions/dotnet-isolated-process-guide?tabs=hostbuilder%2Clinux&WT.mc_id=DOP-MVP-5001655#managing-log-levels logging.Services.Configure<LoggerFilterOptions>(options => { LoggerFilterRule? defaultRule = options.Rules.FirstOrDefault(rule => rule.ProviderName == "Microsoft.Extensions.Logging.ApplicationInsights.ApplicationInsightsLoggerProvider"); if (defaultRule is not null) { options.Rules.Remove(defaultRule); } }); }) .Build(); build.Run(); Log.Warning("After run"); } catch (Exception ex) { Log.Fatal(ex, "An unhandled exception occurred during bootstrapping"); } finally { Log.Warning("Exiting application"); Log.CloseAndFlush(); }
In my experimenting with this, when the Function is closed normally (eg. by being requested to stop in the Azure Portal / or pressing Ctrl-C in the console window when running locally) I was not able to get any logging working in the
finally
block. I think by then it's pretty much game over and the Function Host is keen to wrap things up.But what if the Function is running in Azure? The Debug or Console sinks won't be much use there. In ApplicationInsights sink docs, there's a section on how to flush messages manually. The code sample shows creating a new instance of
TelemetryClient
so that you can use the ApplicationInsights sink in the bootstrap logger.Log.Logger = new LoggerConfiguration() .WriteTo.Console() .WriteTo.Debug() .WriteTo.ApplicationInsights(new TelemetryClient(new TelemetryConfiguration()), new TraceTelemetryConverter()) .CreateBootstrapLogger();
If I simulate a configuration error by throwing an exception inside the
ConfigureServices
call, then you do get data sent to App Insights. eg.{ "name": "AppExceptions", "time": "2025-02-08T06:32:25.4548247Z", "tags": { "ai.cloud.roleInstance": "Delphinium", "ai.internal.sdkVersion": "dotnetc:2.22.0-997" }, "data": { "baseType": "ExceptionData", "baseData": { "ver": 2, "exceptions": [ { "id": 59941933, "outerId": 0, "typeName": "System.InvalidOperationException", "message": "This is a test exception", "hasFullStack": true, "parsedStack": [ { "level": 0, "method": "Program+<>c.<<Main>$>b__0_1", "assembly": "FuncWithSerilog, Version=1.2.6.0, Culture=neutral, PublicKeyToken=null", "fileName": "D:\\git\\azure-function-dotnet-isolated-logging\\net9\\FuncWithSerilog\\Program.cs", "line": 36 }, { "level": 1, "method": "Microsoft.Extensions.Hosting.HostBuilder.InitializeServiceProvider", "assembly": "Microsoft.Extensions.Hosting, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "line": 0 }, { "level": 2, "method": "Microsoft.Extensions.Hosting.HostBuilder.Build", "assembly": "Microsoft.Extensions.Hosting, Version=9.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "line": 0 }, { "level": 3, "method": "Program.<Main>$", "assembly": "FuncWithSerilog, Version=1.2.6.0, Culture=neutral, PublicKeyToken=null", "fileName": "D:\\git\\azure-function-dotnet-isolated-logging\\net9\\FuncWithSerilog\\Program.cs", "line": 21 } ] } ], "severityLevel": "Critical", "properties": { "MessageTemplate": "An unhandled exception occurred during bootstrapping" } } } }
So there you go!
And this is all well and good, but it's important to mention that Microsoft are suggesting for new codebases use OpenTelemetry instead of App Insights! I'll have to check out how that works soon.
-
Farewell Facebook (and Meta)
I've made the decision to delete my Facebook account, and indeed all of my Meta accounts (eg. Messenger, WhatsApp and Instagram).
I think I joined Facebook back in 2007, and yes Facebook has been a great way to keep in touch with family and friends, particularly those further away, so this is not something I've done lightly.
For a while I've had an uneasy feeling about Facebook and its parent company Meta having a decidedly seedier/unpleasant/questionable side. Recent political events in the USA and how Meta has positioned itself in relation to those were the last straw. This also follows my decision last year to delete my Twitter account for similar reasons.
I think there's a bit of a balancing act between being aware of current affairs and the political situation both locally and overseas, and trying to cope with what can seem like an endless stream of awful news. Those recent events in the USA really precipitated this, but they haven't had the monopoly on greedy, selfish, largely very rich people wanting to feather their own nests at the expense of the most vulnerable. There are many ways to express my disapproval or opposition to this. Deleting my account is a tiny, insignificant action on its own. But if it turns out to be part of something bigger, then maybe those people and companies might at least take note, if not change their behaviour.
I have now downloaded all my available data from Facebook (no idea if I'll ever refer to it, but better to get a copy before I go). Note that depending on what data you request, it can take up to 14 days for them to make it available. Weird that it can take that long - maybe there's a lowly work experience kid that is running around restoring data from dusty backup tapes for this!.
I am not leaving social media completely. I'm still on the following:
The Fediverse/Mastdodon
https://mastodon.online/@DavidRGardiner
This is probably my favourite, and I like the 'federated' way it works. So there's not just a single site/server, but you can choose one but still connect with folks on others. Think of it a bit like email - you can have your email hosted on Gmail but still correspond with someone who is using Hotmail.
Bluesky
https://bsky.app/profile/david.gardiner.net.au
The newer kid on the block. An alternative to the Fediverse, and probably because the UI looks so close to old Twitter, a lot of folks who are leaving Twitter are ending up here.
LinkedIn
https://www.linkedin.com/in/davidrgardiner/
This is for work and professional content sharing and connections.
Be aware - I'm fussy about who I connect with on LinkedIn. If I haven't ever met you first or know who you are, I'm almost certainly going to decline your invitation.
Signal
A free and privacy focused instance messaging system. I am here, but tend to keep it private. I will suggest using this to the few people I have chatting with Messenger or WhatsApp.
Email
It's still a great way to keep in touch. Replace the first dot in my blog's address and you've figured out my email address! On the other hand, if you're a nasty spammer then ignore that suggestion.
Blogging
I've been blogging here for almost 20 years! I don't plan to stop, so if you want to keep up with what's got my attention (mostly technical, but sometimes less so) feel free to check back here regularly.
Real life
So catch up with me online using one of those methods, or go 'old school' and the next time you bump into me in real life, we'll have lots to talk about 😀
-
4 years at SixPivot
Another year has passed, and today I clock up 4 years at SixPivot!
Looking back at last year's post, I must say it has been a consistent story.
I'm still enjoying working from home (and my heart goes out to those unfortunate to work for organisations with short-sighted management who are forcing a return to office for no good reasons other than a) they feel lonely in office themselves, b) they're looking at how much they're paying for empty offices or c) they don't trust their employees and figure if they can't see them in the office then how do they know that they're actually working? (To be clear, I think there are some valid reasons to work in an office, but these are not three of them).
I'm still enjoying the flexibility of a morning walk/bike ride/water the garden/pick blackberries/etc before I kick of the work day.
I even more value my SixPivot colleagues. The breadth of experience and opinions is so good. It was great to see them all at our SixPivot Summit last year, and I will again come this July. Plus we have our 'PPP' professional development day coming up in late March where we'll gather in two locations (Brisbane and Melbourne) for a day of technical training and knowledge sharing. (Think a mini 'DDD Conference' but just for Pivots!)
And it's always nice to get 'good vibes' points for my 'workversary'! 4 years means 400 points are mine to spend in the SixPivot rewards shop. Do I buy some gift cards, put them towards my next laptop, phone, a donation to charity or open source, a new SixPivot polo shirt, or maybe all of the above?
I'll be wrapping up my involvement with my current client at the end of February, so I'm looking forward to some new and challenging client engagements soon.