13

I want to use Serilog in an Azure Function v4 (.net 6) (the logs should be sent to Datadog). For this I have installed the following nuget packages:

<PackageReference Include="Serilog" Version="2.10.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="Serilog.Formatting.Compact" Version="1.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.Datadog.Logs" Version="0.3.5" />

Below is the configuration in the Startup.cs class:

public override void Configure(IFunctionsHostBuilder builder)
{
  builder.Services.AddHttpClient();
  
  //... adding services etc.

  Log.Logger = new LoggerConfiguration()
    .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
    .MinimumLevel.Override("Worker", LogEventLevel.Warning)
    .MinimumLevel.Override("Host", LogEventLevel.Warning)
    .MinimumLevel.Override("System", LogEventLevel.Error)
    .MinimumLevel.Override("Function", LogEventLevel.Error)
    .MinimumLevel.Override("Azure.Storage.Blobs", LogEventLevel.Error)
    .MinimumLevel.Override("Azure.Core", LogEventLevel.Error)
    .Enrich.WithProperty("Application", "Comatic.KrediScan.AzureFunctions")
    .Enrich.FromLogContext()
    .WriteTo.DatadogLogs("XXXXXXXXXXX", configuration: new DatadogConfiguration() { Url = "https://http-intake.logs.datadoghq.eu" }, logLevel:   LogEventLevel.Debug)
    .WriteTo.Console()
    .CreateLogger();

  builder.Services.AddSingleton<ILoggerProvider>(sp => new SerilogLoggerProvider(Log.Logger, true));

  builder.Services.AddLogging(lb =>
  {
    //lb.ClearProviders(); //--> if used nothing works...
    lb.AddSerilog(Log.Logger, true);
  });

Basically logging works, but all log statements are written twice (with a few milliseconds difference, Datadog and Console).

enter image description here

Obviously I am doing something fundamentally wrong with the configuration. I don't use appsettings.json, the configuration of Serilog takes place exclusively in the code. I have scoured the entire internet and read just about every article on Serilog and Azure Functions. On Stackoverflow I also read virtually every question about it and tried all the answers. Unfortunately, so far without success.

SO-Questions for example: Use Serilog with Azure Log Stream
How do I use Serilog with Azure WebJobs?
Serilog enricher Dependency Injection with Azure Functions
https://github.com/hgmauri/sample-azure-functions/blob/main/src/Sample.AzureFunctions.DotNet31/Startup.cs

Is there any example for setting up Serilog with Azure Functions v4 / .net 6?

Thanks a lot for the help!
Michael Hachen

mikehachen
  • 256
  • 1
  • 2
  • 8
  • 1
    Perhaps I'm being dumb, but isn't the log written twice because that's what you've told it to do? `.WriteTo.DatadogLogs(...).WriteTo.Console()` – stuartd Feb 08 '22 at 12:53
  • 1
    I have the same configuration in a .net 6 web api => `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 the `WriteTo.Console(...)` - still duplicated logs. – mikehachen Feb 08 '22 at 14:58
  • 2
    Just wanted to say: Great question with evidence of prior attempts/research. Well done! – Pure.Krome Jun 21 '22 at 10:07
  • Great question, helped me to quickly setup my serilog logging for azure functions :D – SwissCoder Jul 05 '22 at 09:49

4 Answers4

8

Got it! After replacing all ILogger with ILogger<T> and removing the line builder.Services.AddSingleton<ILoggerProvider>(sp => new SerilogLoggerProvider(Log.Logger, true)); everything worked as expected.

Startup.cs

Log.Logger = new LoggerConfiguration()
          .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
          .MinimumLevel.Override("Worker", LogEventLevel.Warning)
          .MinimumLevel.Override("Host", LogEventLevel.Warning)
          .MinimumLevel.Override("System", LogEventLevel.Error)
          .MinimumLevel.Override("Function", LogEventLevel.Error)
          .MinimumLevel.Override("Azure.Storage.Blobs", LogEventLevel.Error)
          .MinimumLevel.Override("Azure.Core", LogEventLevel.Error)
          .Enrich.WithProperty("Application", $"xxxxx.AzureFunctions.{builder.GetContext().EnvironmentName}")
          .Enrich.FromLogContext()
          .Enrich.WithExceptionDetails(new DestructuringOptionsBuilder()
            .WithDefaultDestructurers()
            .WithDestructurers(new[] { new SqlExceptionDestructurer() }))
          .WriteTo.Seq(builder.GetContext().EnvironmentName.Equals("Development", StringComparison.OrdinalIgnoreCase) ? "http://localhost:5341/" : "https://xxxxxx.xx:5341/", LogEventLevel.Verbose)
          .WriteTo.Console(theme: SystemConsoleTheme.Literate)
          .CreateLogger();
      
      builder.Services.AddLogging(lb =>
      {
        lb.AddSerilog(Log.Logger, true);
      });
mikehachen
  • 256
  • 1
  • 2
  • 8
  • 6
    could you please share some code on how you configure `Startup.cs` and wire it up. I believe you have use NON-isolated V4/net 6. – user584018 Jun 29 '22 at 12:01
  • 1
    Agree. Showing Startup.cs, Host.json, any other configs (e.g. appsettings.json) and usage of the logger would really help others who have the same problem! – Jack S Feb 24 '23 at 13:44
  • This setup writes double to the console. Did you ever find a way to remove the built-in console logger in Azure Functions? – Kasper Holdum Mar 23 '23 at 09:40
  • For me, this configuration worked. Do you have another log configuration in appsettings.json? – mikehachen Mar 24 '23 at 10:12
4

For those that got here because they can't properly config their logging in an azure function with the app insights sink this is what works for me:

private static void ConfigureLogging(IServiceCollection services)
{
   Log.Logger = new LoggerConfiguration()
        .MinimumLevel.Information()
        .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)
        .MinimumLevel.Override("System", LogEventLevel.Warning)
        .MinimumLevel.Override("Worker", LogEventLevel.Warning)
        .MinimumLevel.Override("Host", LogEventLevel.Warning)
        .MinimumLevel.Override("Function", LogEventLevel.Warning)
        .MinimumLevel.Override("Azure", LogEventLevel.Warning)
        .MinimumLevel.Override("DurableTask", LogEventLevel.Warning)
        .Enrich.FromLogContext()
        .Enrich.WithExceptionDetails()
        .WriteTo.ApplicationInsights(
            TelemetryConfiguration.CreateDefault(),
            TelemetryConverter.Events,
            LogEventLevel.Information)
        .CreateLogger();
        services.AddLogging(configure => configure.AddSerilog(Log.Logger));
}

The example here at the time of writing doesn't seem to work. The logging scope doesn't get captured in the output.

Serilog Version: 2.11.0

Serilog.Sinks.ApplicationInsights Version: 4.0.0

Linked example for the future:

[assembly: FunctionsStartup(typeof(MyFunctions.Startup))]
namespace MyFunctions
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton<ILoggerProvider>((sp) => 
            {
                Log.Logger = new LoggerConfiguration()
                    .Enrich.FromLogContext()
                    .WriteTo.ApplicationInsights(sp.GetRequiredService<TelemetryClient>(), TelemetryConverter.Traces)
                    .CreateLogger();
                return new SerilogLoggerProvider(Log.Logger, true);
            });
        }
    }
}
Plevi
  • 106
  • 3
  • 3
1

To set up Serilog.ILogger instead of ILogger<T> you can add Serilog as Singleton

builder.AddSingleton<ILogger>(Log.Logger);

instead of

services.AddLogging(configure => configure.AddSerilog(Log.Logger));

and then in the function inject ILogger from Serilog instead of ILoggerFactory and log as

_logger.Information("awesome log");

I run isolated function in .net7

CodeNotFound
  • 22,153
  • 10
  • 68
  • 69
alex
  • 41
  • 2
0

For a Functions v4/ Isolated .NET 7 version of this

builder.ConfigureServices((hostingContext, services) =>
{ 
    Log.Logger = new LoggerConfiguration()
        .ReadFrom.Configuration(config)
        .Enrich.FromLogContext()
        .WriteTo.Console()
        .WriteTo.ApplicationInsights(TelemetryConfiguration.CreateDefault(), TelemetryConverter.Traces)
        .CreateLogger();

    services.AddLogging(lb => lb.AddSerilog(Log.Logger, true));
    services.AddApplicationInsightsTelemetryWorkerService();
    services.AddInterventionCalculatorServices(config);//injecting my services
});

Gets you serilog with sinks for console and Insights.

You can also configure the default logger within the hosts.json file

{
  "version": "2.0",
  "logging": {
    "logLevel": {
      "Function.MyFunctionName.User": "Information",
      "Function": "Error"
    }
}

To remove duplicate messages (there may be a way to turn off console entirely from here, but I've not found one).

After this, you should only be seeing your serilog sinks and only 1 set of the important console messages.

Note that recent versions of Visual studio have had some issues with copying files on "F5" (latest patch 17.7.0 Preview 3.0 to VS Community is supposed to resolve this, but I'm not sure its 100%). Make sure to rebuild after changing your hosts.json file and to verify whats in your deployed folder to retain your sanity...

Paul Devenney
  • 889
  • 6
  • 18