From 76bdd61b8f5cb6f89e815e62e7b9524af57cd9e9 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=20Attribute=20exported=20OTLP=20logs?= =?UTF-8?q?=20to=20the=20ServiceControl=20instance?= 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() and no OpenTelemetry resource, so exported log records had no service identity and showed up as "unknown_service" in the backend. LoggerUtil.Initialize(serviceName, serviceVersion) is now called once at process startup (in each instance's Program.cs, before any logger is created) and builds a single OpenTelemetry resource — service.name = instance name, service.version = instance version, plus an auto-generated service.instance.id. Both the host logging pipeline and the static bootstrap loggers (CreateStaticLogger) share that resource, so every exported record — including early startup diagnostics — is attributed to the instance. service.name matches the value used by the Audit metrics resource, so logs and metrics correlate. The resource still uses ResourceBuilder.CreateDefault(), so operators can enrich it with deployment attributes via OTEL_SERVICE_NAME / OTEL_RESOURCE_ATTRIBUTES. --- src/ServiceControl.Audit/Program.cs | 8 ++++++ .../LoggerUtil.cs | 25 ++++++++++++++++++- src/ServiceControl.Monitoring/Program.cs | 6 +++++ src/ServiceControl/Program.cs | 8 ++++++ 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ServiceControl.Audit/Program.cs b/src/ServiceControl.Audit/Program.cs index 8cf01d435c..73a9eff6cc 100644 --- a/src/ServiceControl.Audit/Program.cs +++ b/src/ServiceControl.Audit/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reflection; using Microsoft.Extensions.Logging; using ServiceControl.Audit.Infrastructure.Hosting; @@ -13,6 +14,13 @@ { ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly()); + // Establish the telemetry identity once, before any logger is created, so every logger — including the + // static bootstrap loggers — attributes exported OTLP logs to this instance. Mirrors the InstanceName + // resolution in Settings (InternalQueueName is the legacy fallback for the instance name). + var instanceName = SettingsReader.Read(Settings.SettingsRootNamespace, "InstanceName", + SettingsReader.Read(Settings.SettingsRootNamespace, "InternalQueueName", Settings.DEFAULT_INSTANCE_NAME)); + LoggerUtil.Initialize(instanceName, FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion); + var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace); LoggingConfigurator.ConfigureLogging(loggingSettings); logger = LoggerUtil.CreateStaticLogger(typeof(Program)); diff --git a/src/ServiceControl.Infrastructure/LoggerUtil.cs b/src/ServiceControl.Infrastructure/LoggerUtil.cs index b00617fbe0..86a4931b16 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] @@ -24,6 +25,24 @@ public static class LoggerUtil public static string SeqAddress { private get; set; } + // Telemetry resource attached to exported OTLP logs (service.name/service.version/service.instance.id). + // Set once at process startup via Initialize() — before any logger is created — so both the host pipeline + // and the static bootstrap loggers (CreateStaticLogger) share a single instance identity. Defaults to + // CreateDefault() (which still honors OTEL_SERVICE_NAME/OTEL_RESOURCE_ATTRIBUTES) for the rare logger + // created before Initialize runs. + static ResourceBuilder serviceResourceBuilder = ResourceBuilder.CreateDefault(); + + public static void Initialize(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. + serviceResourceBuilder = ResourceBuilder.CreateDefault() + .AddService(serviceName, serviceVersion: serviceVersion, autoGenerateServiceInstanceId: true); + } + public static bool IsLoggingTo(Loggers logger) { return (logger & ActiveLoggers) == logger; @@ -54,7 +73,11 @@ public static void ConfigureLogging(this ILoggingBuilder loggingBuilder, LogLeve } if (IsLoggingTo(Loggers.Otlp)) { - loggingBuilder.AddOpenTelemetry(configure => configure.AddOtlpExporter()); + loggingBuilder.AddOpenTelemetry(configure => + { + configure.SetResourceBuilder(serviceResourceBuilder); + configure.AddOtlpExporter(); + }); } } diff --git a/src/ServiceControl.Monitoring/Program.cs b/src/ServiceControl.Monitoring/Program.cs index 8635862a91..19bd378d99 100644 --- a/src/ServiceControl.Monitoring/Program.cs +++ b/src/ServiceControl.Monitoring/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reflection; using Microsoft.Extensions.Logging; using ServiceControl.Configuration; @@ -11,6 +12,11 @@ { ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly()); + // Establish the telemetry identity once, before any logger is created, so every logger — including the + // static bootstrap loggers — attributes exported OTLP logs to this instance. + var instanceName = SettingsReader.Read(Settings.SettingsRootNamespace, "InstanceName", Settings.DEFAULT_INSTANCE_NAME); + LoggerUtil.Initialize(instanceName, FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion); + var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace); LoggingConfigurator.ConfigureLogging(loggingSettings); logger = LoggerUtil.CreateStaticLogger(typeof(Program)); diff --git a/src/ServiceControl/Program.cs b/src/ServiceControl/Program.cs index 7234479cd3..824eda9764 100644 --- a/src/ServiceControl/Program.cs +++ b/src/ServiceControl/Program.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Reflection; using Microsoft.Extensions.Logging; using Particular.ServiceControl.Hosting; @@ -13,6 +14,13 @@ { ExeConfiguration.PopulateAppSettings(Assembly.GetExecutingAssembly()); + // Establish the telemetry identity once, before any logger is created, so every logger — including the + // static bootstrap loggers — attributes exported OTLP logs to this instance. Mirrors the InstanceName + // resolution in Settings (InternalQueueName is the legacy fallback for the instance name). + var instanceName = SettingsReader.Read(Settings.SettingsRootNamespace, "InstanceName", + SettingsReader.Read(Settings.SettingsRootNamespace, "InternalQueueName", Settings.DEFAULT_INSTANCE_NAME)); + LoggerUtil.Initialize(instanceName, FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).ProductVersion); + var loggingSettings = new LoggingSettings(Settings.SettingsRootNamespace); LoggingConfigurator.ConfigureLogging(loggingSettings); logger = LoggerUtil.CreateStaticLogger(typeof(Program));