diff --git a/src/EventStore.Core.XUnit.Tests/OpenTelemetry/TelemetryServiceIdentityTests.cs b/src/EventStore.Core.XUnit.Tests/OpenTelemetry/TelemetryServiceIdentityTests.cs new file mode 100644 index 0000000000..d01db07d0b --- /dev/null +++ b/src/EventStore.Core.XUnit.Tests/OpenTelemetry/TelemetryServiceIdentityTests.cs @@ -0,0 +1,37 @@ +using System; +using System.Linq; +using EventStore.Common.Utils; +using EventStore.Core.Diagnostics; +using FluentAssertions; +using TrogonEventStore.SemanticConventions; +using Xunit; + +namespace EventStore.Core.XUnit.Tests.OpenTelemetry; + +public class TelemetryServiceIdentityTests +{ + [Fact] + public void UsesTheSameIdentityForAttributeDictionariesAndResources() + { + var identity = TelemetryServiceIdentity.ForComponent("test-node"); + var dictionary = identity.CreateAttributeDictionary(); + var resource = identity.CreateResourceBuilder().Build().Attributes + .ToDictionary(attribute => attribute.Key, attribute => attribute.Value); + + dictionary.Should().Contain(AttributeNames.ServiceName, "eventstore"); + dictionary.Should().Contain(AttributeNames.ServiceInstanceId, "test-node"); + dictionary.Should().Contain(AttributeNames.ServiceVersion, VersionInfo.Version); + resource.Should().Contain(dictionary); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void RejectsMissingComponentNames(string componentName) + { + var action = () => TelemetryServiceIdentity.ForComponent(componentName); + + action.Should().Throw(); + } +} diff --git a/src/EventStore.Core/ClusterVNode.cs b/src/EventStore.Core/ClusterVNode.cs index c7ff3af7e1..39e9b9d2b8 100644 --- a/src/EventStore.Core/ClusterVNode.cs +++ b/src/EventStore.Core/ClusterVNode.cs @@ -25,6 +25,7 @@ using EventStore.Core.Cluster; using EventStore.Core.Data; using EventStore.Core.DataStructures; +using EventStore.Core.Diagnostics; using EventStore.Core.Helpers; using EventStore.Core.Index; using EventStore.Core.Index.Hashes; @@ -1761,6 +1762,7 @@ await Db.Open(!options.Database.SkipDbVerify, threads: options.Database.Initiali options.Interface.DisableStatsOnHttp, configuration, trackers, + TelemetryServiceIdentity.ForComponent(options.GetComponentName()), nodeInformationProvider, options.Cluster.DiscoverViaDns ? options.Cluster.ClusterDns : null, ConfigureNodeServices, diff --git a/src/EventStore.Core/ClusterVNodeStartup.cs b/src/EventStore.Core/ClusterVNodeStartup.cs index 427c26f890..ee116e925f 100644 --- a/src/EventStore.Core/ClusterVNodeStartup.cs +++ b/src/EventStore.Core/ClusterVNodeStartup.cs @@ -7,6 +7,7 @@ using EventStore.Common.Utils; using EventStore.Core.Bus; using EventStore.Core.Configuration; +using EventStore.Core.Diagnostics; using EventStore.Core.Messages; using EventStore.Core.Services.Storage.ReaderIndex; using EventStore.Core.Services.Transport.Grpc; @@ -27,7 +28,6 @@ using Microsoft.Extensions.Diagnostics.HealthChecks; using OpenTelemetry; using OpenTelemetry.Metrics; -using OpenTelemetry.Resources; using OpenTelemetry.Trace; using ClientGossip = EventStore.Core.Services.Transport.Grpc.Gossip; using ClusterGossip = EventStore.Core.Services.Transport.Grpc.Cluster.Gossip; @@ -51,6 +51,7 @@ public class ClusterVNodeStartup : IInternalStartup, IHandle configureNodeServices, @@ -106,6 +108,8 @@ public ClusterVNodeStartup( _disableHttpMetrics = disableHttpMetrics; _configuration = configuration; _trackers = trackers; + _telemetryServiceIdentity = telemetryServiceIdentity ?? + throw new ArgumentNullException(nameof(telemetryServiceIdentity)); _nodeInformationProvider = nodeInformationProvider ?? throw new ArgumentNullException(nameof(nodeInformationProvider)); _clusterDns = clusterDns; @@ -225,8 +229,15 @@ public void ConfigureServicesOnly(IServiceCollection services) .AddSingleton() .AddOpenTelemetry() - .WithMetrics(meterOptions => ConfigureMetrics(meterOptions, metricsConfiguration, _configuration)) - .WithTracing(tracerOptions => ConfigureTracing(tracerOptions, _configuration)) + .WithMetrics(meterOptions => ConfigureMetrics( + meterOptions, + metricsConfiguration, + _configuration, + _telemetryServiceIdentity)) + .WithTracing(tracerOptions => ConfigureTracing( + tracerOptions, + _configuration, + _telemetryServiceIdentity)) .Services .AddGrpcHealthChecks(options => { @@ -285,10 +296,11 @@ public void ConfigureServicesOnly(IServiceCollection services) private static void ConfigureMetrics( MeterProviderBuilder meterOptions, MetricsConfiguration metricsConfiguration, - IConfiguration configuration) + IConfiguration configuration, + TelemetryServiceIdentity serviceIdentity) { meterOptions - .SetResourceBuilder(CreateResourceBuilder()) + .SetResourceBuilder(serviceIdentity.CreateResourceBuilder()) .AddMeter(metricsConfiguration.Meters) .AddView(i => { @@ -377,9 +389,10 @@ private static void ConfigureOtlpMetrics( private static void ConfigureTracing( TracerProviderBuilder tracerOptions, - IConfiguration configuration) + IConfiguration configuration, + TelemetryServiceIdentity serviceIdentity) { - tracerOptions.SetResourceBuilder(CreateResourceBuilder()); + tracerOptions.SetResourceBuilder(serviceIdentity.CreateResourceBuilder()); if (!configuration.OtlpTracesEnabled()) { @@ -394,11 +407,6 @@ private static void ConfigureTracing( exporterOptions)); } - private static ResourceBuilder CreateResourceBuilder() => - ResourceBuilder.CreateDefault().AddService( - serviceName: "eventstore", - serviceVersion: VersionInfo.Version); - public void Handle(SystemMessage.SystemReady _) => _nodeHealthState.MarkReady(); public void Handle(SystemMessage.BecomeShuttingDown _) => _nodeHealthState.MarkShuttingDown(); diff --git a/src/EventStore.Core/Diagnostics/TelemetryServiceIdentity.cs b/src/EventStore.Core/Diagnostics/TelemetryServiceIdentity.cs new file mode 100644 index 0000000000..400dc513c9 --- /dev/null +++ b/src/EventStore.Core/Diagnostics/TelemetryServiceIdentity.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using EventStore.Common.Utils; +using OpenTelemetry.Resources; +using TrogonEventStore.SemanticConventions; + +namespace EventStore.Core.Diagnostics; + +public sealed record TelemetryServiceIdentity +{ + private const string DefaultServiceName = "eventstore"; + + public string ServiceName { get; } + public string ServiceInstanceId { get; } + public string ServiceVersion { get; } + + private TelemetryServiceIdentity(string name, string instanceId, string version) + { + ServiceName = name; + ServiceInstanceId = instanceId; + ServiceVersion = version; + } + + public static TelemetryServiceIdentity ForComponent(string componentName) + { + ArgumentException.ThrowIfNullOrWhiteSpace(componentName); + return new TelemetryServiceIdentity(DefaultServiceName, componentName, VersionInfo.Version); + } + + public Dictionary CreateAttributeDictionary() => + new(GetAttributes()); + + public ResourceBuilder CreateResourceBuilder() => + ResourceBuilder.CreateDefault().AddAttributes(GetAttributes()); + + private IEnumerable> GetAttributes() + { + yield return new KeyValuePair(AttributeNames.ServiceName, ServiceName); + yield return new KeyValuePair(AttributeNames.ServiceInstanceId, ServiceInstanceId); + yield return new KeyValuePair(AttributeNames.ServiceVersion, ServiceVersion); + } +} diff --git a/src/EventStore.Core/Log/OpenTelemetryLogger.cs b/src/EventStore.Core/Log/OpenTelemetryLogger.cs index fd005c02bc..928f6267e3 100644 --- a/src/EventStore.Core/Log/OpenTelemetryLogger.cs +++ b/src/EventStore.Core/Log/OpenTelemetryLogger.cs @@ -1,14 +1,12 @@ using System; -using System.Collections.Generic; -using EventStore.Common.Utils; using EventStore.Core.Configuration; +using EventStore.Core.Diagnostics; using Microsoft.Extensions.Configuration; using OpenTelemetry.Exporter; using OpenTelemetry.Logs; using Serilog; using Serilog.Filters; using Serilog.Sinks.OpenTelemetry; -using TrogonEventStore.SemanticConventions; namespace EventStore.Common.Log; @@ -18,6 +16,17 @@ public static LoggerConfiguration AddOpenTelemetryLogger( this LoggerConfiguration loggerConfiguration, IConfiguration configuration, string componentName, + Action configureOtlp = null) => + AddOpenTelemetryLogger( + loggerConfiguration, + configuration, + TelemetryServiceIdentity.ForComponent(componentName), + configureOtlp); + + private static LoggerConfiguration AddOpenTelemetryLogger( + LoggerConfiguration loggerConfiguration, + IConfiguration configuration, + TelemetryServiceIdentity serviceIdentity, Action configureOtlp = null) { if (configuration is null || !configuration.OtlpLogsEnabled()) @@ -36,12 +45,7 @@ public static LoggerConfiguration AddOpenTelemetryLogger( .Filter.ByExcluding(Matching.FromSource("REGULAR-STATS-LOGGER")) .WriteTo.OpenTelemetry(options => { - options.ResourceAttributes = new Dictionary - { - [AttributeNames.ServiceName] = "eventstore", - [AttributeNames.ServiceInstanceId] = componentName, - [AttributeNames.ServiceVersion] = VersionInfo.Version - }; + options.ResourceAttributes = serviceIdentity.CreateAttributeDictionary(); options.Protocol = otlpExporterConfig.Protocol switch { OtlpExportProtocol.Grpc => OtlpProtocol.Grpc,