diff --git a/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbLogEnricherNames.cs b/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbLogEnricherNames.cs
new file mode 100644
index 0000000..a1b602e
--- /dev/null
+++ b/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbLogEnricherNames.cs
@@ -0,0 +1,27 @@
+namespace ZB.MOM.WW.Telemetry.Serilog;
+
+///
+/// Canonical Serilog property name constants for the identity enrichers stamped by
+/// . Use these constants — not literal strings —
+/// when querying properties in sinks or tests. Each property mirrors a shared OTel Resource
+/// attribute so logs and metrics/traces from the same node carry identical dimensions.
+///
+public static class ZbLogEnricherNames
+{
+ ///
+ /// Serilog property: physical or logical site identifier. Matches OTel Resource site.id.
+ ///
+ public const string SiteId = "SiteId";
+
+ ///
+ /// Serilog property: node function (central, site, hub, standalone).
+ /// Matches OTel Resource node.role.
+ ///
+ public const string NodeRole = "NodeRole";
+
+ ///
+ /// Serilog property: machine name ().
+ /// Matches OTel Resource host.name. Populated automatically — not a caller-supplied option.
+ ///
+ public const string NodeHostname = "NodeHostname";
+}
diff --git a/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbSerilogConfig.cs b/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbSerilogConfig.cs
new file mode 100644
index 0000000..416d27d
--- /dev/null
+++ b/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbSerilogConfig.cs
@@ -0,0 +1,69 @@
+using System;
+using Serilog;
+using Serilog.Configuration;
+using ZB.MOM.WW.Telemetry;
+
+namespace ZB.MOM.WW.Telemetry.Serilog;
+
+///
+/// Reusable seam that applies the shared ZB.MOM.WW logging configuration (identity enrichers,
+/// trace-context correlation, redaction, and OTel log export) to a
+/// . Shared by
+/// and unit tests so both exercise an identical enricher/sink set.
+///
+public static class ZbSerilogConfig
+{
+ ///
+ /// Applies the shared identity enrichers — and
+ /// from , and
+ /// from
+ /// (auto, never a caller-supplied option) — to
+ /// . SiteId/NodeRole are stamped only when
+ /// the option is non-null/non-empty, mirroring the shared OTel Resource omission rules.
+ ///
+ /// The Serilog configuration to enrich.
+ /// The telemetry options describing the service identity.
+ /// The same for chaining.
+ public static LoggerConfiguration Apply(
+ LoggerConfiguration loggerConfiguration,
+ ZbTelemetryOptions options) =>
+ Apply(loggerConfiguration, options, serviceProvider: null);
+
+ ///
+ /// Overload of that additionally
+ /// wires the service-provider-dependent stages — the redaction enricher (which lazily resolves
+ /// a registered ILogRedactor). When is null, only the
+ /// provider-independent enrichers are applied.
+ ///
+ /// The Serilog configuration to enrich.
+ /// The telemetry options describing the service identity.
+ ///
+ /// Provider used to lazily resolve project-supplied seams (e.g. ILogRedactor);
+ /// may be null in tests or pipelines without DI.
+ ///
+ /// The same for chaining.
+ public static LoggerConfiguration Apply(
+ LoggerConfiguration loggerConfiguration,
+ ZbTelemetryOptions options,
+ IServiceProvider? serviceProvider)
+ {
+ ArgumentNullException.ThrowIfNull(loggerConfiguration);
+ ArgumentNullException.ThrowIfNull(options);
+
+ LoggerEnrichmentConfiguration enrich = loggerConfiguration.Enrich;
+
+ if (!string.IsNullOrEmpty(options.SiteId))
+ {
+ enrich.WithProperty(ZbLogEnricherNames.SiteId, options.SiteId);
+ }
+
+ if (!string.IsNullOrEmpty(options.NodeRole))
+ {
+ enrich.WithProperty(ZbLogEnricherNames.NodeRole, options.NodeRole);
+ }
+
+ enrich.WithProperty(ZbLogEnricherNames.NodeHostname, Environment.MachineName);
+
+ return loggerConfiguration;
+ }
+}
diff --git a/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbSerilogExtensions.cs b/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbSerilogExtensions.cs
new file mode 100644
index 0000000..b22d107
--- /dev/null
+++ b/ZB.MOM.WW.Telemetry/src/ZB.MOM.WW.Telemetry.Serilog/ZbSerilogExtensions.cs
@@ -0,0 +1,69 @@
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Serilog;
+using Serilog.Events;
+using ZB.MOM.WW.Telemetry;
+
+namespace ZB.MOM.WW.Telemetry.Serilog;
+
+///
+/// Extension point for configuring the shared Serilog bootstrap on an
+/// . Wires config-driven sinks
+/// (ReadFrom.Configuration), an explicit minimum level (Serilog:MinimumLevel,
+/// default ), and the shared enricher/redaction/OTel-export
+/// set via . Does NOT configure OTel metrics/traces — call
+/// AddZbTelemetry in the core package for that.
+///
+public static class ZbSerilogExtensions
+{
+ ///
+ /// Registers Serilog on the host with the shared ZB.MOM.WW configuration: sinks read from
+ /// (ReadFrom.Configuration), an explicit minimum level
+ /// (Serilog:MinimumLevel, default ), and the
+ /// identity enrichers (SiteId/NodeRole from ,
+ /// NodeHostname = ). The
+ /// delegate receives the same
+ /// used by AddZbTelemetry — typically share one options-population lambda across both.
+ ///
+ /// The host application builder.
+ /// Populates the .
+ public static IHostApplicationBuilder AddZbSerilog(
+ this IHostApplicationBuilder builder,
+ Action configure)
+ {
+ ArgumentNullException.ThrowIfNull(builder);
+ ArgumentNullException.ThrowIfNull(configure);
+
+ var options = new ZbTelemetryOptions();
+ configure(options);
+
+ var minimumLevel = ReadMinimumLevel(builder.Configuration);
+
+ builder.Services.AddSerilog((serviceProvider, loggerConfiguration) =>
+ {
+ loggerConfiguration
+ .ReadFrom.Configuration(builder.Configuration)
+ .MinimumLevel.Is(minimumLevel);
+
+ ZbSerilogConfig.Apply(loggerConfiguration, options, serviceProvider);
+ });
+
+ return builder;
+ }
+
+ ///
+ /// Reads Serilog:MinimumLevel (or the nested Serilog:MinimumLevel:Default) from
+ /// configuration, falling back to for missing or
+ /// unparseable values.
+ ///
+ private static LogEventLevel ReadMinimumLevel(IConfiguration configuration)
+ {
+ var raw = configuration["Serilog:MinimumLevel"]
+ ?? configuration["Serilog:MinimumLevel:Default"];
+
+ return Enum.TryParse(raw, ignoreCase: true, out var parsed)
+ ? parsed
+ : LogEventLevel.Information;
+ }
+}
diff --git a/ZB.MOM.WW.Telemetry/tests/ZB.MOM.WW.Telemetry.Serilog.Tests/EnricherTests.cs b/ZB.MOM.WW.Telemetry/tests/ZB.MOM.WW.Telemetry.Serilog.Tests/EnricherTests.cs
new file mode 100644
index 0000000..2fc138f
--- /dev/null
+++ b/ZB.MOM.WW.Telemetry/tests/ZB.MOM.WW.Telemetry.Serilog.Tests/EnricherTests.cs
@@ -0,0 +1,46 @@
+using Serilog;
+using Serilog.Events;
+using Serilog.Sinks.InMemory;
+using ZB.MOM.WW.Telemetry;
+using ZB.MOM.WW.Telemetry.Serilog;
+
+namespace ZB.MOM.WW.Telemetry.Serilog.Tests;
+
+public sealed class EnricherTests
+{
+ private static string ScalarValue(LogEvent logEvent, string propertyName)
+ {
+ Assert.True(
+ logEvent.Properties.TryGetValue(propertyName, out var value),
+ $"expected property '{propertyName}' to be present");
+ var scalar = Assert.IsType(value);
+ return scalar.Value?.ToString() ?? "";
+ }
+
+ [Fact]
+ public void Identity_enrichers_stamp_SiteId_NodeRole_and_NodeHostname()
+ {
+ var sink = new InMemorySink();
+ var options = new ZbTelemetryOptions
+ {
+ ServiceName = "otopcua",
+ SiteId = "s1",
+ NodeRole = "Central",
+ };
+
+ var loggerConfig = new LoggerConfiguration();
+ ZbSerilogConfig.Apply(loggerConfig, options);
+ using var logger = loggerConfig
+ .WriteTo.Sink(sink)
+ .CreateLogger();
+
+ logger.Information("hello");
+
+ var logEvent = Assert.Single(sink.LogEvents);
+ Assert.Equal("s1", ScalarValue(logEvent, ZbLogEnricherNames.SiteId));
+ Assert.Equal("Central", ScalarValue(logEvent, ZbLogEnricherNames.NodeRole));
+ Assert.Equal(
+ Environment.MachineName,
+ ScalarValue(logEvent, ZbLogEnricherNames.NodeHostname));
+ }
+}