From 560fba38a41902189459fff0014fa9fcf73d7364 Mon Sep 17 00:00:00 2001 From: Ramon Smits Date: Mon, 15 Jun 2026 16:41:13 +0200 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Add=20service.name/version=20to=20O?= =?UTF-8?q?TLP=20log=20resource?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The OTLP log exporter was registered with a bare AddOtlpExporter(), so exported logs arrived with no resource and showed up as "unknown_service" in the backend. Attach service.name, service.version and an auto-generated service.instance.id to the OTLP log resource for all three instances (ServiceControl, Audit, Monitoring), sourced from the instance name and assembly version — matching the resource already configured on the Audit metrics path so logs and metrics correlate by service. The resource still uses ResourceBuilder.CreateDefault(), so operators can enrich it with deployment-specific attributes via OTEL_SERVICE_NAME / OTEL_RESOURCE_ATTRIBUTES. --- .../HostApplicationBuilderExtensions.cs | 2 +- .../LoggerUtil.cs | 31 +++++++++++++++++-- .../ServiceControlComponentRunner.cs | 2 +- .../HostApplicationBuilderExtensions.cs | 5 ++- .../PersistenceTestBase.cs | 2 +- .../HostApplicationBuilderExtensions.cs | 8 ++--- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs index 151d7b9315..6c5828dbd1 100644 --- a/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Audit/HostApplicationBuilderExtensions.cs @@ -39,7 +39,7 @@ public static void AddServiceControlAudit(this IHostApplicationBuilder builder, RecordStartup(settings, configuration, persistenceConfiguration); builder.Logging.ClearProviders(); - builder.Logging.ConfigureLogging(settings.LoggingSettings.LogLevel); + builder.Logging.ConfigureLogging(settings.LoggingSettings.LogLevel, settings.InstanceName, InstanceVersion); var services = builder.Services; var transportSettings = settings.ToTransportSettings(); diff --git a/src/ServiceControl.Infrastructure/LoggerUtil.cs b/src/ServiceControl.Infrastructure/LoggerUtil.cs index b00617fbe0..0457e32edc 100644 --- a/src/ServiceControl.Infrastructure/LoggerUtil.cs +++ b/src/ServiceControl.Infrastructure/LoggerUtil.cs @@ -6,6 +6,7 @@ using Microsoft.Extensions.Logging; using NLog.Extensions.Logging; using OpenTelemetry.Logs; + using OpenTelemetry.Resources; using ServiceControl.Infrastructure.TestLogger; [Flags] @@ -29,7 +30,24 @@ public static bool IsLoggingTo(Loggers logger) return (logger & ActiveLoggers) == logger; } - public static void ConfigureLogging(this ILoggingBuilder loggingBuilder, LogLevel level) + // Configures the host logging pipeline. serviceName/serviceVersion identify this instance and are + // attached to exported OTLP telemetry as resource attributes (service.name/service.version) so logs + // are attributable instead of arriving as "unknown_service". They are mandatory: the host always knows + // its instance name and version, and the alternative (defaulting them) is what produced unattributable logs. + public static void ConfigureLogging(this ILoggingBuilder loggingBuilder, LogLevel level, string serviceName, string serviceVersion) + { + ArgumentException.ThrowIfNullOrEmpty(serviceName); + ArgumentException.ThrowIfNullOrEmpty(serviceVersion); + + // CreateDefault() also reads OTEL_SERVICE_NAME/OTEL_RESOURCE_ATTRIBUTES, so operators can still enrich + // the resource with deployment-specific attributes via those environment variables. + var resourceBuilder = ResourceBuilder.CreateDefault() + .AddService(serviceName, serviceVersion: serviceVersion, autoGenerateServiceInstanceId: true); + + loggingBuilder.AddProviders(level, resourceBuilder); + } + + static void AddProviders(this ILoggingBuilder loggingBuilder, LogLevel level, ResourceBuilder resourceBuilder) { loggingBuilder.SetMinimumLevel(level); @@ -54,7 +72,11 @@ public static void ConfigureLogging(this ILoggingBuilder loggingBuilder, LogLeve } if (IsLoggingTo(Loggers.Otlp)) { - loggingBuilder.AddOpenTelemetry(configure => configure.AddOtlpExporter()); + loggingBuilder.AddOpenTelemetry(configure => + { + configure.SetResourceBuilder(resourceBuilder); + configure.AddOtlpExporter(); + }); } } @@ -64,7 +86,10 @@ static ILoggerFactory GetOrCreateLoggerFactory(LogLevel level) { if (!_factories.TryGetValue(level, out var factory)) { - factory = LoggerFactory.Create(configure => configure.ConfigureLogging(level)); + // Static/bootstrap loggers are created from scattered call sites that have no instance identity + // (and many run before it is known). They use the default resource, which still honors + // OTEL_SERVICE_NAME/OTEL_RESOURCE_ATTRIBUTES if the operator sets them. + factory = LoggerFactory.Create(configure => configure.AddProviders(level, ResourceBuilder.CreateDefault())); _factories[level] = factory; } diff --git a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs index a5d7623d79..c674d7a073 100644 --- a/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs +++ b/src/ServiceControl.Monitoring.AcceptanceTests/TestSupport/ServiceControlComponentRunner.cs @@ -98,7 +98,7 @@ async Task InitializeServiceControl(ScenarioContext context) EnvironmentName = Environments.Development }); hostBuilder.Logging.ClearProviders(); - hostBuilder.Logging.ConfigureLogging(LogLevel.Information); + hostBuilder.Logging.ConfigureLogging(LogLevel.Information, settings.InstanceName, "0.0.0"); hostBuilder.Logging.AddContextAppender(context); hostBuilder.Services.AddScenarioContext(context); diff --git a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs index 3316da03a5..634c9d5932 100644 --- a/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl.Monitoring/HostApplicationBuilderExtensions.cs @@ -1,6 +1,7 @@ namespace ServiceControl.Monitoring; using System; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -26,11 +27,13 @@ namespace ServiceControl.Monitoring; public static class HostApplicationBuilderExtensions { + static readonly string InstanceVersion = FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion; + public static void AddServiceControlMonitoring(this IHostApplicationBuilder hostBuilder, Func onCriticalError, Settings settings, EndpointConfiguration endpointConfiguration) { - hostBuilder.Logging.ConfigureLogging(settings.LoggingSettings.LogLevel); + hostBuilder.Logging.ConfigureLogging(settings.LoggingSettings.LogLevel, settings.InstanceName, InstanceVersion); var services = hostBuilder.Services; diff --git a/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs b/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs index fdd41695c7..97f5903c09 100644 --- a/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs +++ b/src/ServiceControl.Persistence.Tests/PersistenceTestBase.cs @@ -34,7 +34,7 @@ public async Task SetUp() var hostBuilder = Host.CreateApplicationBuilder(); LoggerUtil.ActiveLoggers = Loggers.Test; - hostBuilder.Logging.ConfigureLogging(LogLevel.Information); + hostBuilder.Logging.ConfigureLogging(LogLevel.Information, "ServiceControl.Persistence.Tests", "0.0.0"); await PersistenceTestsContext.Setup(hostBuilder); diff --git a/src/ServiceControl/HostApplicationBuilderExtensions.cs b/src/ServiceControl/HostApplicationBuilderExtensions.cs index d8a6ab6b81..b3cfaaf2e7 100644 --- a/src/ServiceControl/HostApplicationBuilderExtensions.cs +++ b/src/ServiceControl/HostApplicationBuilderExtensions.cs @@ -30,6 +30,8 @@ namespace Particular.ServiceControl static class HostApplicationBuilderExtensions { + static readonly string InstanceVersion = FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion; + public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, Settings settings, EndpointConfiguration configuration, params ReadOnlySpan components) { ArgumentNullException.ThrowIfNull(configuration); @@ -42,7 +44,7 @@ public static void AddServiceControl(this IHostApplicationBuilder hostBuilder, S } hostBuilder.Logging.ClearProviders(); - hostBuilder.Logging.ConfigureLogging(settings.LoggingSettings.LogLevel); + hostBuilder.Logging.ConfigureLogging(settings.LoggingSettings.LogLevel, settings.InstanceName, InstanceVersion); var componentSetupContext = new ComponentInstallationContext(); var serviceControlComponents = components is { Length: 0 } ? ServiceControlMainInstance.Components : components; @@ -112,11 +114,9 @@ public static void AddServiceControlInstallers(this IHostApplicationBuilder host static void RecordStartup(Settings settings, EndpointConfiguration endpointConfiguration) { - var version = FileVersionInfo.GetVersionInfo(typeof(HostApplicationBuilderExtensions).Assembly.Location).ProductVersion; - var startupMessage = $@" ------------------------------------------------------------- -ServiceControl Version: {version} +ServiceControl Version: {InstanceVersion} Audit Retention Period (optional): {settings.AuditRetentionPeriod} Error Retention Period: {settings.ErrorRetentionPeriod} Ingest Error Messages: {settings.IngestErrorMessages}