Add OpenTelemetry instrumentation

This commit is contained in:
Shadowghost 2026-05-09 18:53:08 +02:00
parent 169745fddb
commit 95f84de20a
6 changed files with 224 additions and 0 deletions

View file

@ -53,6 +53,12 @@
<PackageVersion Include="Moq" Version="4.18.4" />
<PackageVersion Include="NEbml" Version="1.1.0.5" />
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<PackageVersion Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Extensions.Hosting" Version="1.15.3" />
<PackageVersion Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.15.2" />
<PackageVersion Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.15.1-beta.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.15.1" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.15.1" />
<PackageVersion Include="PlaylistsNET" Version="1.4.1" />
<PackageVersion Include="prometheus-net.AspNetCore" Version="8.2.1" />
<PackageVersion Include="prometheus-net.DotNetRuntime" Version="4.4.1" />

View file

@ -0,0 +1,122 @@
using System;
using System.Reflection;
using MediaBrowser.Model.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using OpenTelemetry.Exporter;
using OpenTelemetry.Logs;
using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
namespace Jellyfin.Server.Extensions
{
/// <summary>
/// Extension methods for wiring up OpenTelemetry tracing, metrics and logging.
/// </summary>
public static class OpenTelemetryServiceCollectionExtensions
{
private const string DefaultServiceName = "jellyfin";
/// <summary>
/// Registers OpenTelemetry pipelines based on the provided <see cref="OpenTelemetryOptions"/>.
/// When <see cref="OpenTelemetryOptions.Enabled"/> is <c>false</c> this method is a no-op.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="options">Server-side OpenTelemetry options.</param>
/// <returns>The service collection, for chaining.</returns>
public static IServiceCollection AddJellyfinOpenTelemetry(this IServiceCollection services, OpenTelemetryOptions options)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(options);
if (!options.Enabled)
{
return services;
}
var serviceName = string.IsNullOrWhiteSpace(options.ServiceName) ? DefaultServiceName : options.ServiceName;
var serviceVersion = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "unknown";
var otel = services.AddOpenTelemetry()
.ConfigureResource(r => r.AddService(serviceName: serviceName, serviceVersion: serviceVersion));
if (options.EnableTraces)
{
otel.WithTracing(tracing =>
{
if (options.InstrumentAspNetCore)
{
tracing.AddAspNetCoreInstrumentation();
}
if (options.InstrumentHttpClient)
{
tracing.AddHttpClientInstrumentation();
}
if (options.InstrumentEntityFrameworkCore)
{
tracing.AddEntityFrameworkCoreInstrumentation();
}
tracing.AddOtlpExporter(o => ConfigureOtlp(o, options));
});
}
if (options.EnableMetrics)
{
otel.WithMetrics(metrics =>
{
if (options.InstrumentAspNetCore)
{
metrics.AddAspNetCoreInstrumentation();
}
if (options.InstrumentHttpClient)
{
metrics.AddHttpClientInstrumentation();
}
if (options.InstrumentRuntime)
{
metrics.AddRuntimeInstrumentation();
}
metrics.AddOtlpExporter(o => ConfigureOtlp(o, options));
});
}
if (options.EnableLogs)
{
services.AddLogging(b => b.AddOpenTelemetry(o =>
{
o.SetResourceBuilder(ResourceBuilder.CreateDefault().AddService(serviceName: serviceName, serviceVersion: serviceVersion));
o.IncludeFormattedMessage = true;
o.IncludeScopes = true;
o.AddOtlpExporter(opt => ConfigureOtlp(opt, options));
}));
}
return services;
}
private static void ConfigureOtlp(OtlpExporterOptions exporter, OpenTelemetryOptions options)
{
exporter.Protocol = options.OtlpProtocol == OpenTelemetryOtlpProtocol.HttpProtobuf
? OtlpExportProtocol.HttpProtobuf
: OtlpExportProtocol.Grpc;
if (!string.IsNullOrWhiteSpace(options.OtlpEndpoint)
&& Uri.TryCreate(options.OtlpEndpoint, UriKind.Absolute, out var endpoint))
{
exporter.Endpoint = endpoint;
}
if (!string.IsNullOrWhiteSpace(options.OtlpHeaders))
{
exporter.Headers = options.OtlpHeaders;
}
}
}
}

View file

@ -46,6 +46,12 @@
<PackageReference Include="CommandLineParser" />
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" />
<PackageReference Include="Morestachio" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" />
<PackageReference Include="OpenTelemetry.Instrumentation.Runtime" />
<PackageReference Include="prometheus-net" />
<PackageReference Include="prometheus-net.AspNetCore" />
<PackageReference Include="Serilog.AspNetCore" />

View file

@ -69,6 +69,7 @@ namespace Jellyfin.Server
services.AddJellyfinApi(_serverApplicationHost.GetApiPluginAssemblies(), _serverConfigurationManager.GetNetworkConfiguration());
services.AddJellyfinDbContext(_serverApplicationHost.ConfigurationManager, _configuration);
services.AddJellyfinApiSwagger();
services.AddJellyfinOpenTelemetry(_serverConfigurationManager.Configuration.OpenTelemetry);
// configure custom legacy authentication
services.AddCustomAuthentication();

View file

@ -0,0 +1,84 @@
namespace MediaBrowser.Model.Configuration;
/// <summary>
/// The OTLP transport protocol to use when exporting telemetry.
/// </summary>
public enum OpenTelemetryOtlpProtocol
{
/// <summary>
/// gRPC over HTTP/2. Default OTLP endpoint is http://localhost:4317.
/// </summary>
Grpc = 0,
/// <summary>
/// Protobuf payloads over HTTP/1.1. Default OTLP endpoint is http://localhost:4318.
/// </summary>
HttpProtobuf = 1
}
/// <summary>
/// Settings controlling the OpenTelemetry pipeline. Disabled by default.
/// </summary>
public class OpenTelemetryOptions
{
/// <summary>
/// Gets or sets a value indicating whether OpenTelemetry instrumentation and export are enabled.
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Gets or sets a value indicating whether traces are collected and exported.
/// </summary>
public bool EnableTraces { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether metrics are collected and exported.
/// </summary>
public bool EnableMetrics { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether logs are exported via OpenTelemetry.
/// </summary>
public bool EnableLogs { get; set; }
/// <summary>
/// Gets or sets a value indicating whether ASP.NET Core requests are instrumented.
/// </summary>
public bool InstrumentAspNetCore { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether outgoing HTTP client calls are instrumented.
/// </summary>
public bool InstrumentHttpClient { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether Entity Framework Core database commands are instrumented.
/// Backed by a prerelease OpenTelemetry instrumentation package.
/// </summary>
public bool InstrumentEntityFrameworkCore { get; set; } = true;
/// <summary>
/// Gets or sets a value indicating whether .NET runtime metrics (GC, thread pool, exceptions, etc.) are collected.
/// </summary>
public bool InstrumentRuntime { get; set; } = true;
/// <summary>
/// Gets or sets the service name reported in telemetry. Defaults to "jellyfin" when null or empty.
/// </summary>
public string? ServiceName { get; set; }
/// <summary>
/// Gets or sets the OTLP endpoint URL. When null or empty the SDK default for the configured protocol is used.
/// </summary>
public string? OtlpEndpoint { get; set; }
/// <summary>
/// Gets or sets the OTLP protocol. The default is gRPC.
/// </summary>
public OpenTelemetryOtlpProtocol OtlpProtocol { get; set; } = OpenTelemetryOtlpProtocol.Grpc;
/// <summary>
/// Gets or sets optional OTLP headers in the form "key1=value1,key2=value2". Useful for vendor authentication tokens.
/// </summary>
public string? OtlpHeaders { get; set; }
}

View file

@ -288,4 +288,9 @@ public class ServerConfiguration : BaseApplicationConfiguration
/// Gets or sets a value indicating whether old authorization methods are allowed.
/// </summary>
public bool EnableLegacyAuthorization { get; set; } = true;
/// <summary>
/// Gets or sets the OpenTelemetry options.
/// </summary>
public OpenTelemetryOptions OpenTelemetry { get; set; } = new OpenTelemetryOptions();
}