Serilog sink with OpenTelemetry

I’m running into some issues when trying to assign an additional Serilog sink through code.

Specifically, I’m trying to add the OpenTelemetry sink, which doesn’t seems to have any Appsettings features.

From what I can see in Umbraco’s source code, sinks are assigned via RegisteredReloadableLogger.cs.
Like this (from Umbraco’s code):

services.AddSingleton(sp =>
{
    var logger = new RegisteredReloadableLogger(Log.Logger as ReloadableLogger);

    logger.Reload(cfg =>
    {
        cfg.MinimalConfiguration(hostEnvironment, loggingConfig, umbracoFileConfiguration)
            .ReadFrom.Configuration(configuration)
            .ReadFrom.Services(sp);

        return cfg;
    });

    return logger;
});

However, since RegisteredReloadableLoggerhas an internal class, this way is not possible.

This issue seems similar to the one described here:

Is there another supported way to register a Serilog sink in parallel with Umbraco’s?

Hi @dsm-twoday this is how we got it working for my V13 project:

using System.Reflection;
using Azure.Extensions.AspNetCore.Configuration.Secrets;
using Azure.Identity;
using Project.Core;
using Project.Core.Logging;
using Serilog;
using Serilog.Formatting.Json;
using Umbraco.Cms.Core.Logging;

namespace Project.UmbracoCMS
{
    public class Program
    {
        public static void Main(string args)
            => CreateHostBuilder(args)
            .Build()
            .Run();

    public static IHostBuilder CreateHostBuilder(string[] args)
    {
        var builder = WebApplication.CreateBuilder(args);
        
        Log.Logger = new LoggerConfiguration()
            .ReadFrom.Configuration(builder.Configuration)
            .Enrich.FromLogContext()
            .Enrich.WithProperty(Constants.Logging.EnvironmentName, builder.Environment.EnvironmentName)
            .CreateBootstrapLogger();

        return Host.CreateDefaultBuilder(args)
            .ConfigureUmbracoDefaults()
            .ConfigureAppConfiguration((context, config) =>
            {
                var settings = config.Build();
                if (context.HostingEnvironment.IsDevelopment())
                {
                    config
                        .AddEnvironmentVariables()
                        .AddUserSecrets(Assembly.GetExecutingAssembly(), true);
                }
                else
                {
                    var keyVaultEndpoint = settings["AppSettings:AzureKeyVault-Uri"];
                    if (!string.IsNullOrEmpty(keyVaultEndpoint) && Uri.TryCreate(keyVaultEndpoint, UriKind.Absolute, out var validUri))
                    {
                        config.AddAzureKeyVault(
                            validUri,
                            new DefaultAzureCredential(),
                            new AzureKeyVaultConfigurationOptions
                            {
                                ReloadInterval = TimeSpan.FromHours(1)
                            });
                    }
                }
            })
            .UseSerilog((context, services, loggerConfiguration) =>
            {
                var hostingEnv = services.GetRequiredService<Umbraco.Cms.Core.Hosting.IHostingEnvironment>();
                var loggingConfiguration = services.GetRequiredService<ILoggingConfiguration>();

                loggerConfiguration
                .MinimalConfiguration(hostingEnv, loggingConfiguration, context.Configuration)
                    .ReadFrom.Configuration(context.Configuration)
                    .ReadFrom.Services(services)
                    .Enrich.FromLogContext()                        
                    .Enrich.With(services.GetService<TelemetryLogEventEnricher>())
                    .Enrich.WithProperty(Constants.Logging.EnvironmentName, context.HostingEnvironment.EnvironmentName)
                    .Enrich.WithProperty(Constants.Logging.ApplicationName, "Portal")
                    .Enrich.WithProperty(Constants.Logging.TeamName,        "xxxxx")
                    .Enrich.WithProperty(Constants.Logging.ProjectName,     "xxxxx")
                    .WriteTo.Console()
                    .WriteTo.AzureEventHub(
                        formatter: new JsonFormatter(),
                        connectionString: context.Configuration["AppSettings:AzureEventHub-ConnectionString"],
                        eventHubName: context.Configuration["AppSettings:AzureEventHub-Name"]
                    );
            })
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStaticWebAssets();
                webBuilder.UseStartup<Startup>();
            }); 
    }
}
}

Then we did the actual enriching stuff in the TelemetryLogEventEnricher

Support issues suggest there is some Serilog.Settings.Configuration via appsetings support…

Configure Logger headers declaratively with JSON · Issue #120 · serilog/serilog-sinks-opentelemetry
(with header support added here)

I think that most sinks will support simple parameter types, but maybe not complex ones without correct configuration binders for deserialization :thinking:

serilog-sinks-opentelemetry/src/Serilog.Sinks.OpenTelemetry/Sinks/OpenTelemetry/OpenTelemetrySinkOptions.cs at main · serilog/serilog-sinks-opentelemetry

maybe…

{
  "Serilog": {
    "Using": ["Serilog.Sinks.OpenTelemetry"],
    "MinimumLevel": "Information",
    "WriteTo": [
      {
        "Name": "OpenTelemetry",
        "Args": {
          "endpoint": "http://localhost:4317",
          "protocol": "Grpc",
          "resourceAttributes": {
            "service.name": "my-service",
            "deployment.environment": "production"
          },
          "headers": {
                 "x-seq-apikey": "__SeqApiKey__"
           }
        }
      }
    ]
  }
}

Maybe move to async too.. :wink:

1 Like

You’re right, thanks!

Setting it up based on the example and changing the protocol to HttpProtobuf, it seems to work as intended. :+1:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.