I'm answering it for Azure Functions v4 (Isolated mode).
Firstly, the Functions.csproj file structure is outlined below, allowing one to add the Serilog packages I am using.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>latest</LangVersion>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
<!-- Application Insights isn't enabled by default. See https://aka.ms/AAt8mw4. -->
<!-- <PackageReference Include="Microsoft.ApplicationInsights.WorkerService" Version="2.22.0" /> -->
<!-- <PackageReference Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.0.0" /> -->
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="2.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.0.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.ServiceBus" Version="5.22.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" />
<PackageReference Include="Polly" Version="8.5.1" />
<PackageReference Include="Refit.HttpClientFactory" Version="8.0.0" />
<PackageReference Include="Serilog" Version="4.2.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.0" />
<PackageReference Include="Serilog.Settings.Configuration" Version="9.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.Seq" Version="9.0.0" />
</ItemGroup>
<ItemGroup>
<None Update="host.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Using Include="System.Threading.ExecutionContext" Alias="ExecutionContext" />
</ItemGroup>
</Project>
Second Program.cs as below
using ADS.Functions.Http;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using Serilog;
var builder = FunctionsApplication.CreateBuilder(args);
builder.Services.ConfigureServices(builder.Configuration);
try
{
var app = builder.Build();
Log.Information("Starting ADS.Functions...");
app.Run();
}
catch (Exception ex)
{
Log.Fatal(ex, "ADS.Functions startup failed.");
}
finally
{
Log.CloseAndFlush();
}
And then service extensions are shown below.
using System.Text.Json;
using ADS.Functions.Services;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Refit;
using Serilog;
namespace ADS.Functions.Http;
public static class ServiceExtensions
{
public static void ConfigureServices(this IServiceCollection services, IConfiguration configuration)
{
services.ConfigureLogging(configuration);
}
private static void ConfigureLogging(this IServiceCollection services, IConfiguration configuration)
{
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(configuration.GetSection("Serilog"))
.Enrich.FromLogContext()
.Enrich.WithProperty("ApplicationName", "ADS Functions")
.WriteTo.Console()
.CreateLogger();
services.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders(); // Removes default Azure Functions logging
loggingBuilder.AddSerilog();
});
}
private static void ConfigureMtpApiHttpClient(this IServiceCollection services, IConfiguration configuration)
{
var apiBaseUrl = GetMtpApiBaseUrl(configuration);
var refitSettings = CreateRefitSettings();
services.AddRefitClient<IMtpApiAuthentication>(refitSettings)
.ConfigureHttpClient(client => client.BaseAddress = apiBaseUrl);
services.AddTransient<MtpAuthenticationHandler>();
services.AddRefitClient<IMtpApi>(refitSettings)
.ConfigureHttpClient(client => client.BaseAddress = apiBaseUrl)
.AddHttpMessageHandler<MtpAuthenticationHandler>();
}
private static Uri GetMtpApiBaseUrl(IConfiguration configuration)
{
var baseUrl = configuration["MtpApi:BaseUrl"];
if (string.IsNullOrWhiteSpace(baseUrl))
{
Log.Fatal("MTP API Base URL is missing in configuration.");
throw new InvalidOperationException("MTP API Base URL is required.");
}
return new Uri(baseUrl);
}
private static RefitSettings CreateRefitSettings()
{
var jsonOptions = new JsonSerializerOptions
{
PropertyNamingPolicy = null // Ensures PascalCase serialization
};
return new RefitSettings
{
ContentSerializer = new SystemTextJsonContentSerializer(jsonOptions)
};
}
}
Finally the local.settings.json file as below
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
},
"Serilog": {
"Using": ["Serilog.Sinks.Console", "Serilog.Sinks.Seq"],
"LevelSwitches": {
"$appLogLevel": "Information",
"$seqSwitch": "Information",
"$consoleSwitch": "Information"
},
"MinimumLevel": {
"ControlledBy": "$appLogLevel",
"Override": {
"Default": "Warning",
"Host": "Warning",
"Function": "Warning",
"System": "Warning",
"Microsoft": "Warning",
"Azure.Core": "Warning",
"Worker": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore": "Warning",
"Microsoft.AspNetCore.Authentication": "Warning"
}
},
"WriteTo": [
{
"Name": "Console",
"Args": {
"levelSwitch": "$consoleSwitch",
"theme": "Serilog.Sinks.SystemConsole.Themes.SystemConsoleTheme::Literate, Serilog.Sinks.Console",
"outputTemplate": "[{Timestamp:HH:mm:ss} {Level:u3}] {SourceContext} {Message:lj}{NewLine}{Exception}"
}
},
{
"Name": "Seq",
"Args": {
"levelSwitch": "$seqSwitch",
"serverUrl": "http://localhost:5341",
"outputTemplate": "{Timestamp:HH:mm:ss} [{Level:u3}] {SourceContext} {Message}{NewLine}{Exception} {Properties:j}",
"shared": true
}
}
],
"Enrich": ["FromLogContext", "WithMachineName"]
}
}
If configured correctly, you will only see a single log from Serilog.

.WriteTo.DatadogLogs(...).WriteTo.Console()WriteTo.Console(...)and.WriteTo.DatadogLogs(...)without any duplication of the error logs. So if there is not something completely different between a .net 6 web api and a .net 6 Azure Functions this should not cause the duplication of the logs. And I removed theWriteTo.Console(...)- still duplicated logs.