Skip to content

Commit

Permalink
Convert Seq component to use OTEL's AddProcessor instead of `AddOtl…
Browse files Browse the repository at this point in the history
…pExporter` (#3697)

* Switch Seq OpenTelemetry configuration from `AddOtlpExporter` to `AddProcessor`.

* Switch Seq OpenTelemetry configuration from `AddOtlpExporter` to `AddProcessor`.

* Move OTLP exporter options onto SeqSettings.cs

* Add test

* Update README

* Renamed Seq settings

* Change order of config

* Updated code doc

* ConfigurationSchema.json

* Throw an exception if ServerUrl is unspecified, but HealthChecks is enabled.

* Fix tests

* PR feedback

Fix #3546
  • Loading branch information
liammclennan authored Apr 16, 2024
1 parent 3a601cb commit e58ae7d
Show file tree
Hide file tree
Showing 10 changed files with 246 additions and 33 deletions.
7 changes: 7 additions & 0 deletions Aspire.sln
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OrleansClient", "playground
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OrleansContracts", "playground\orleans\OrleansContracts\OrleansContracts.csproj", "{75760E8A-7025-4F6A-B152-6622688D0E7D}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Aspire.Seq.Tests", "tests\Aspire.Seq.Tests\Aspire.Seq.Tests.csproj", "{0505F739-6F85-4502-A554-77E0D7325F26}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -1173,6 +1175,10 @@ Global
{75760E8A-7025-4F6A-B152-6622688D0E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{75760E8A-7025-4F6A-B152-6622688D0E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{75760E8A-7025-4F6A-B152-6622688D0E7D}.Release|Any CPU.Build.0 = Release|Any CPU
{0505F739-6F85-4502-A554-77E0D7325F26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0505F739-6F85-4502-A554-77E0D7325F26}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0505F739-6F85-4502-A554-77E0D7325F26}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0505F739-6F85-4502-A554-77E0D7325F26}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -1386,6 +1392,7 @@ Global
{8191109E-130C-47F3-B84E-82070A6CD269} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
{906B5687-31AD-4364-AD9F-B4B26113462D} = {8BAF2119-8370-4E9E-A887-D92506F8C727}
{75760E8A-7025-4F6A-B152-6622688D0E7D} = {8BAF2119-8370-4E9E-A887-D92506F8C727}
{0505F739-6F85-4502-A554-77E0D7325F26} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}
Expand Down
2 changes: 1 addition & 1 deletion playground/seq/Seq.AppHost/aspire-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"seq": {
"type": "container.v0",
"connectionString": "{seq.bindings.http.url}",
"image": "datalust/seq:2024.1",
"image": "docker.io/datalust/seq:2024.2",
"env": {
"ACCEPT_EULA": "Y"
},
Expand Down
2 changes: 1 addition & 1 deletion src/Aspire.Hosting.Seq/SeqContainerImageTags.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ internal static class SeqContainerImageTags
{
public const string Registry = "docker.io";
public const string Image = "datalust/seq";
public const string Tag = "2024.1";
public const string Tag = "2024.2";
}
70 changes: 42 additions & 28 deletions src/Components/Aspire.Seq/AspireSeqExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.HealthChecks;
using OpenTelemetry;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Trace;
Expand All @@ -23,11 +24,18 @@ public static class AspireSeqExtensions
/// <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)
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;
builder.Configuration.GetSection("Aspire:Seq").Bind(settings);

if (builder.Configuration.GetConnectionString(connectionName) is string connectionString)
Expand All @@ -37,39 +45,45 @@ public static void AddSeqEndpoint(this IHostApplicationBuilder builder, string c

configureSettings?.Invoke(settings);

if (string.IsNullOrEmpty(settings.ServerUrl))
if (!string.IsNullOrEmpty(settings.ServerUrl))
{
settings.ServerUrl = "http://localhost:5341";
settings.Logs.Endpoint = new Uri($"{settings.ServerUrl}/ingest/otlp/v1/logs");
settings.Traces.Endpoint = new Uri($"{settings.ServerUrl}/ingest/otlp/v1/traces");
}

builder.Services.Configure<OpenTelemetryLoggerOptions>(logging => logging.AddOtlpExporter(opt =>
if (!string.IsNullOrEmpty(settings.ApiKey))
{
opt.Endpoint = new Uri($"{settings.ServerUrl}/ingest/otlp/v1/logs");
opt.Protocol = OtlpExportProtocol.HttpProtobuf;
if (!string.IsNullOrEmpty(settings.ApiKey))
{
opt.Headers = $"X-Seq-ApiKey={settings.ApiKey}";
}
}));
builder.Services.ConfigureOpenTelemetryTracerProvider(tracing => tracing
.AddOtlpExporter(opt =>
{
opt.Endpoint = new Uri($"{settings.ServerUrl}/ingest/otlp/v1/traces");
opt.Protocol = OtlpExportProtocol.HttpProtobuf;
if (!string.IsNullOrEmpty(settings.ApiKey))
{
opt.Headers = $"X-Seq-ApiKey={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.HealthChecks)
{
builder.TryAddHealthCheck(new HealthCheckRegistration(
"Seq",
_ => new SeqHealthCheck(settings.ServerUrl),
failureStatus: default,
tags: default));
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.");
}
}

}
}
96 changes: 95 additions & 1 deletion src/Components/Aspire.Seq/ConfigurationSchema.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,103 @@
"type": "boolean",
"description": "Gets or sets a boolean value that indicates whetherthe Seq server health check is enabled or not."
},
"Logs": {
"type": "object",
"properties": {
"BatchExportProcessorOptions": {
"type": "object",
"properties": {
"ExporterTimeoutMilliseconds": {
"type": "integer"
},
"MaxExportBatchSize": {
"type": "integer"
},
"MaxQueueSize": {
"type": "integer"
},
"ScheduledDelayMilliseconds": {
"type": "integer"
}
},
"description": "Gets or sets the BatchExportProcessor options. Ignored unless ExportProcessorType is Batch."
},
"Endpoint": {
"type": "string",
"format": "uri"
},
"ExportProcessorType": {
"enum": [
"Simple",
"Batch"
],
"description": "Gets or sets the export processor type to be used with the OpenTelemetry Protocol Exporter. The default value is 'OpenTelemetry.ExportProcessorType.Batch'."
},
"Headers": {
"type": "string"
},
"Protocol": {
"enum": [
"Grpc",
"HttpProtobuf"
]
},
"TimeoutMilliseconds": {
"type": "integer"
}
},
"description": "Gets OTLP exporter options for logs."
},
"ServerUrl": {
"type": "string",
"description": "Gets or sets the base URL of the Seq server (including protocol and port). E.g. \"https://example.seq.com:6789\""
"description": "Gets or sets the base URL of the Seq server (including protocol and port). E.g. \"https://example.seq.com:6789. Overrides endpoints set on Logs and Traces .\""
},
"Traces": {
"type": "object",
"properties": {
"BatchExportProcessorOptions": {
"type": "object",
"properties": {
"ExporterTimeoutMilliseconds": {
"type": "integer"
},
"MaxExportBatchSize": {
"type": "integer"
},
"MaxQueueSize": {
"type": "integer"
},
"ScheduledDelayMilliseconds": {
"type": "integer"
}
},
"description": "Gets or sets the BatchExportProcessor options. Ignored unless ExportProcessorType is Batch."
},
"Endpoint": {
"type": "string",
"format": "uri"
},
"ExportProcessorType": {
"enum": [
"Simple",
"Batch"
],
"description": "Gets or sets the export processor type to be used with the OpenTelemetry Protocol Exporter. The default value is 'OpenTelemetry.ExportProcessorType.Batch'."
},
"Headers": {
"type": "string"
},
"Protocol": {
"enum": [
"Grpc",
"HttpProtobuf"
]
},
"TimeoutMilliseconds": {
"type": "integer"
}
},
"description": "Gets OTLP exporter options for traces."
}
},
"description": "Provides the client configuration settings for connecting telemetry to a Seq server."
Expand Down
2 changes: 2 additions & 0 deletions src/Components/Aspire.Seq/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ Aspire.Seq.SeqSettings.ApiKey.get -> string?
Aspire.Seq.SeqSettings.ApiKey.set -> void
Aspire.Seq.SeqSettings.HealthChecks.get -> bool
Aspire.Seq.SeqSettings.HealthChecks.set -> void
Aspire.Seq.SeqSettings.Logs.get -> OpenTelemetry.Exporter.OtlpExporterOptions!
Aspire.Seq.SeqSettings.SeqSettings() -> void
Aspire.Seq.SeqSettings.ServerUrl.get -> string?
Aspire.Seq.SeqSettings.ServerUrl.set -> void
Aspire.Seq.SeqSettings.Traces.get -> OpenTelemetry.Exporter.OtlpExporterOptions!
Microsoft.Extensions.Hosting.AspireSeqExtensions
static Microsoft.Extensions.Hosting.AspireSeqExtensions.AddSeqEndpoint(this Microsoft.Extensions.Hosting.IHostApplicationBuilder! builder, string! connectionName, System.Action<Aspire.Seq.SeqSettings!>? configureSettings = null) -> void
3 changes: 2 additions & 1 deletion src/Components/Aspire.Seq/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,8 @@ Also you can pass the `Action<SeqSettings> configureSettings` delegate to set up
```csharp
builder.AddSeqEndpoint("seq", settings => {
settings.HealthChecks = false;
settings.ServerUrl = "http://localhost:5341"
settings.ServerUrl = "http://localhost:5341";
settings.Logs.TimeoutMilliseconds = 10000;
});
```

Expand Down
14 changes: 13 additions & 1 deletion src/Components/Aspire.Seq/SeqSettings.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using OpenTelemetry.Exporter;

namespace Aspire.Seq;

/// <summary>
Expand All @@ -19,7 +21,17 @@ public sealed class SeqSettings
public string? ApiKey { get; set; }

/// <summary>
/// Gets or sets the base URL of the Seq server (including protocol and port). E.g. "https://example.seq.com:6789"
/// Gets or sets the base URL of the Seq server (including protocol and port). E.g. "https://example.seq.com:6789. Overrides endpoints set on <c>Logs</c> and <c>Traces</c>."
/// </summary>
public string? ServerUrl { get; set; }

/// <summary>
/// Gets OTLP exporter options for logs.
/// </summary>
public OtlpExporterOptions Logs { get; } = new ();

/// <summary>
/// Gets OTLP exporter options for traces.
/// </summary>
public OtlpExporterOptions Traces { get; } = new ();
}
16 changes: 16 additions & 0 deletions tests/Aspire.Seq.Tests/Aspire.Seq.Tests.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>$(NetCurrent)</TargetFramework>
<RunTestsOnHelix>true</RunTestsOnHelix>
<Nullable>enable</Nullable>
<RootNamespace>Aspire.Seq</RootNamespace>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\src\Components\Aspire.Seq\Aspire.Seq.csproj" />
<ProjectReference Include="..\Aspire.Components.Common.Tests\Aspire.Components.Common.Tests.csproj" />

</ItemGroup>

</Project>
67 changes: 67 additions & 0 deletions tests/Aspire.Seq.Tests/SeqEndpointCanBeConfigured.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Extensions.Hosting;
using OpenTelemetry.Exporter;
using Xunit;

namespace Aspire.Seq.Tests;

public class SeqTests
{
[Fact]
public void SeqEndpointCanBeConfigured()
{
var builder = Host.CreateEmptyApplicationBuilder(null);
builder.AddSeqEndpoint("seq", s =>
{
s.HealthChecks = false;
s.Logs.TimeoutMilliseconds = 1000;
s.Traces.Protocol = OtlpExportProtocol.Grpc;
});

using var host = builder.Build();
}

[Fact]
public void ServerUrlSettingOverridesExporterEndpoints()
{
var builder = Host.CreateEmptyApplicationBuilder(null);
var serverUrl = "http://localhost:9876";

SeqSettings settings = new SeqSettings();

builder.AddSeqEndpoint("seq", s =>
{
settings = s;
s.ServerUrl = serverUrl;
s.ApiKey = "ABCDE12345";
s.Logs.Endpoint = new Uri("http://localhost:1234/ingest/otlp/v1/logs");
s.Traces.Endpoint = new Uri("http://localhost:1234/ingest/otlp/v1/traces");
});

Assert.Equal(settings.Logs.Endpoint, new Uri("http://localhost:9876/ingest/otlp/v1/logs"));
Assert.Equal(settings.Traces.Endpoint, new Uri("http://localhost:9876/ingest/otlp/v1/traces"));
}

[Fact]
public void ApiKeySettingIsMergedWithConfiguredHeaders()
{
var builder = Host.CreateEmptyApplicationBuilder(null);

SeqSettings settings = new SeqSettings();

builder.AddSeqEndpoint("seq", s =>
{
settings = s;
s.HealthChecks = false;
s.ApiKey = "ABCDE12345";
s.Logs.Headers = "speed=fast,quality=good";
s.Traces.Headers = "quality=good,speed=fast";
});

Assert.Equal("speed=fast,quality=good,X-Seq-ApiKey=ABCDE12345", settings.Logs.Headers);
Assert.Equal("quality=good,speed=fast,X-Seq-ApiKey=ABCDE12345", settings.Traces.Headers);
}
}

0 comments on commit e58ae7d

Please sign in to comment.