feat: add telemetry support with OpenTelemetry and Seq configuration

This commit is contained in:
Stevan Freeborn
2025-05-23 20:01:55 -05:00
parent 606818b00e
commit 7dce08516b
11 changed files with 107 additions and 3 deletions
+1
View File
@@ -4,6 +4,7 @@
"cSpell.words": [
"Reconnectable",
"Steves",
"stevesbot",
"Szalay"
],
}
+1 -1
View File
@@ -10,7 +10,7 @@ internal static class Extensions
var discordOptions = sp.GetRequiredService<DiscordClientOptions>();
c.BaseAddress = new Uri(discordOptions.ApiUrl);
c.DefaultRequestHeaders.Authorization = new("Bot", discordOptions.AppToken);
c.DefaultRequestHeaders.Add("User-Agent", $"DiscordBot (https://github.com/StevanFreeborn/steves-bot, 0.0.0)");
c.DefaultRequestHeaders.Add("User-Agent", $"DiscordBot (https://github.com/StevanFreeborn/steves-bot, {Instrumentation.SourceVersion})");
})
.AddStandardResilienceHandler();
@@ -1,3 +1,5 @@
using Activity = StevesBot.Worker.Discord.Gateway.Events.Data.Activity;
namespace StevesBot.Worker.Discord.Gateway;
internal sealed class DiscordGatewayClient : IDiscordGatewayClient
@@ -201,7 +203,7 @@ internal sealed class DiscordGatewayClient : IDiscordGatewayClient
}
catch (OperationCanceledException ex)
{
_logger.LogInformation(ex, "Receive messages operation canceled: {Message}", ex.Message);
_logger.LogInformation(ex, "Receive messages operation canceled");
}
#pragma warning disable CA1031 // Do not catch general exception types
catch (Exception ex)
@@ -1,3 +1,5 @@
using Activity = StevesBot.Worker.Discord.Gateway.Events.Data.Activity;
namespace StevesBot.Worker.Discord.Gateway.Events;
internal sealed record UpdatePresenceDiscordEvent : DiscordEvent
+3
View File
@@ -11,6 +11,8 @@ builder.Services.AddSingleton(static sp =>
return discordOptions;
});
builder.AddTelemetry();
builder.Services.AddSingleton<IWebSocketFactory, WebSocketFactory>();
builder.Services.AddSingleton(TimeProvider.System);
@@ -23,4 +25,5 @@ builder.Services.AddDiscordGatewayClient(static (client) =>
builder.Services.AddHostedService<Worker>();
var host = builder.Build();
await host.RunAsync();
@@ -8,6 +8,11 @@
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Http" Version="9.0.5" />
<PackageReference Include="Microsoft.Extensions.Http.Resilience" Version="9.5.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.12.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.12.0" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,54 @@
namespace StevesBot.Worker.Telemetry;
internal static class Extensions
{
public static HostApplicationBuilder AddTelemetry(this HostApplicationBuilder builder)
{
builder.Services.AddSingleton<Instrumentation>();
var seq = new SeqOptions();
builder.Configuration.GetSection(nameof(SeqOptions)).Bind(seq);
if (seq.IsEnabled is false)
{
return builder;
}
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource =>
{
resource.AddService(Instrumentation.SourceName, Instrumentation.SourceVersion);
resource.AddAttributes(new Dictionary<string, object>
{
["service.name"] = Instrumentation.SourceName,
["service.version"] = Instrumentation.SourceVersion,
["service.instance.id"] = Environment.MachineName,
["service.namespace"] = "stevesbot",
["service.environment"] = Environment.GetEnvironmentVariable("DOTNET_ENVIRONMENT") ?? "production",
});
})
.WithLogging(lb =>
{
lb.AddOtlpExporter(o =>
{
o.Protocol = OtlpExportProtocol.HttpProtobuf;
o.Endpoint = new Uri(seq.LogEndpoint);
o.Headers = seq.AuthHeader;
});
})
.WithTracing(tb =>
{
tb.AddSource(Instrumentation.SourceName);
tb.AddAspNetCoreInstrumentation();
tb.AddHttpClientInstrumentation();
tb.AddOtlpExporter(o =>
{
o.Protocol = OtlpExportProtocol.HttpProtobuf;
o.Endpoint = new Uri(seq.TraceEndpoint);
o.Headers = seq.AuthHeader;
});
});
return builder;
}
}
@@ -0,0 +1,13 @@
namespace StevesBot.Worker.Telemetry;
internal sealed class Instrumentation : IDisposable
{
public const string SourceName = "StevesBot.Worker";
public const string SourceVersion = "0.0.0";
public readonly ActivitySource Source = new(SourceName, SourceVersion);
public void Dispose()
{
Source.Dispose();
}
}
@@ -0,0 +1,12 @@
namespace StevesBot.Worker.Telemetry;
internal sealed class SeqOptions
{
public string ServerUrl { get; set; } = string.Empty;
public string ApiKeyHeader { get; set; } = string.Empty;
public string ApiKey { get; set; } = string.Empty;
public bool IsEnabled => !string.IsNullOrWhiteSpace(ServerUrl) && !string.IsNullOrWhiteSpace(ApiKeyHeader) && !string.IsNullOrWhiteSpace(ApiKey);
public string LogEndpoint => $"{ServerUrl}/ingest/otlp/v1/logs";
public string TraceEndpoint => $"{ServerUrl}/ingest/otlp/v1/traces";
public string AuthHeader => $"{ApiKeyHeader}={ApiKey}";
}
+7
View File
@@ -1,3 +1,4 @@
global using System.Diagnostics;
global using System.Net.Http.Json;
global using System.Net.WebSockets;
global using System.Reflection;
@@ -7,6 +8,11 @@ global using System.Text.Json.Serialization;
global using Microsoft.Extensions.Options;
global using OpenTelemetry.Exporter;
global using OpenTelemetry.Logs;
global using OpenTelemetry.Resources;
global using OpenTelemetry.Trace;
global using StevesBot.Worker;
global using StevesBot.Worker.Discord;
global using StevesBot.Worker.Discord.Gateway;
@@ -17,5 +23,6 @@ global using StevesBot.Worker.Discord.Rest.Requests;
global using StevesBot.Worker.Discord.Rest.Responses;
global using StevesBot.Worker.Discord.Shared;
global using StevesBot.Worker.Handlers;
global using StevesBot.Worker.Telemetry;
global using StevesBot.Worker.Threading;
global using StevesBot.Worker.WebSockets;
@@ -9,5 +9,10 @@
"ApiUrl": "ApiUrl",
"AppToken": "AppToken",
"Intents": 0
},
"SeqOptions": {
"ServerUrl": "ServerUrl",
"ApiKeyHeader": "ApiKeyHeader",
"ApiKey": "ApiKey"
}
}