File: AspireSeqExtensions.cs
Web Access
Project: src\src\Components\Aspire.Seq\Aspire.Seq.csproj (Aspire.Seq)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using Aspire;
using Aspire.Seq;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Trace;
 
namespace Microsoft.Extensions.Hosting;
 
/// <summary>
/// Extension methods for connecting a project's OpenTelemetry log events and spans to Seq.
/// </summary>
public static class AspireSeqExtensions
{
    private const string DefaultConfigSectionName = "Aspire:Seq";
 
    /// <summary>
    /// Registers OTLP log and trace exporters to send to Seq.
    /// </summary>
    /// <param name="builder">The <see cref="IHostApplicationBuilder" /> to read config from and add services to.</param>
    /// <param name="connectionName">A name used to retrieve the connection string from the ConnectionStrings configuration section.</param>
    /// <param name="configureSettings">An optional delegate that can be used for customizing options. It's invoked after the settings are read from the configuration.</param>
    public static void AddSeqEndpoint(
        this IHostApplicationBuilder builder,
        string connectionName,
        Action<SeqSettings>? configureSettings = null)
    {
        ArgumentNullException.ThrowIfNull(builder);
 
        var settings = new SeqSettings();
        settings.Logs.Protocol = OtlpExportProtocol.HttpProtobuf;
        settings.Traces.Protocol = OtlpExportProtocol.HttpProtobuf;
        settings.Logs.ExportProcessorType = ExportProcessorType.Batch;
        settings.Traces.ExportProcessorType = ExportProcessorType.Batch;
 
        var configSection = builder.Configuration.GetSection(DefaultConfigSectionName);
        var namedConfigSection = builder.Configuration.GetSection(connectionName);
        configSection.Bind(settings);
        namedConfigSection.Bind(settings);
 
        if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
        {
            settings.ServerUrl = connectionString;
        }
 
        configureSettings?.Invoke(settings);
 
        if (!string.IsNullOrEmpty(settings.ServerUrl))
        {
            settings.Logs.Endpoint = new Uri($"{settings.ServerUrl}/ingest/otlp/v1/logs");
            settings.Traces.Endpoint = new Uri($"{settings.ServerUrl}/ingest/otlp/v1/traces");
        }
        if (!string.IsNullOrEmpty(settings.ApiKey))
        {
            settings.Logs.Headers = string.IsNullOrEmpty(settings.Logs.Headers) ? $"X-Seq-ApiKey={settings.ApiKey}" : $"{settings.Logs.Headers},X-Seq-ApiKey={settings.ApiKey}";
            settings.Traces.Headers = string.IsNullOrEmpty(settings.Traces.Headers) ? $"X-Seq-ApiKey={settings.ApiKey}" : $"{settings.Traces.Headers},X-Seq-ApiKey={settings.ApiKey}";
        }
 
        builder.Services.Configure<OpenTelemetryLoggerOptions>(logging => logging.AddProcessor(
            _ => settings.Logs.ExportProcessorType switch {
                ExportProcessorType.Batch => new BatchLogRecordExportProcessor(new OtlpLogExporter(settings.Logs)),
                _ => new SimpleLogRecordExportProcessor(new OtlpLogExporter(settings.Logs))
            }));
 
        builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing.AddProcessor(
            _ => settings.Traces.ExportProcessorType switch {
                ExportProcessorType.Batch => new BatchActivityExportProcessor(new OtlpTraceExporter(settings.Traces)),
                _ => new SimpleActivityExportProcessor(new OtlpTraceExporter(settings.Traces))
            }));
 
        if (!settings.DisableHealthChecks)
        {
            if (settings.ServerUrl is not null)
            {
                builder.TryAddHealthCheck(new HealthCheckRegistration(
                    "Seq",
                    _ => new SeqHealthCheck(settings.ServerUrl),
                    failureStatus: default,
                    tags: default));
            }
            else
            {
                throw new InvalidOperationException(
                    "Unable to add a Seq health check because the 'ServerUrl' setting is missing.");
            }
        }
 
    }
}