feat(telemetry): Telemetry 0.3.0 - service.version, site.id/node.role, logs over OTLP (scadaproj#55)
v2-ci / unit-tests (push) Has been cancelled
v2-ci / unit-tests (push) Has been cancelled
Pins ZB.MOM.WW.Telemetry(.Serilog) 0.1.0 -> 0.3.0. The OTel Resource now names the node: site.id = its application Cluster id (the cluster-<ClusterId> role suffix, omitted on a legacy node), node.role = its plain roles in canonical order (admin / driver / admin,driver), service.version = the entry assembly's informational version (the git SHA). Program passes its resolved EffectiveRoles. The whole OtOpcUa:Telemetry section goes through the shared ApplyConfiguration, so SiteId / NodeRole / ServiceVersion / ResourceAttributes:* are operator overrides, and Exporter=Otlp now also sends the Serilog logs to the same collector. ObservabilityResourceIdentityTests cover the mapping and the built container's Resource. Host.Tests 288/288, Host.IntegrationTests 325/327 (2 skipped). Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using ZB.MOM.WW.LocalDb.Replication;
|
||||
using ZB.MOM.WW.OtOpcUa.Cluster;
|
||||
using ZB.MOM.WW.OtOpcUa.Commons.Observability;
|
||||
using ZB.MOM.WW.OtOpcUa.Driver.Galaxy;
|
||||
using ZB.MOM.WW.Telemetry;
|
||||
@@ -46,23 +47,67 @@ public static class ObservabilityExtensions
|
||||
/// Configuration read for the opt-in OTLP exporter. <c>OtOpcUa:Telemetry:Exporter</c>
|
||||
/// (parsed case-insensitively to <see cref="ZbExporter"/>) switches to OTLP when set to
|
||||
/// <c>Otlp</c>; <c>OtOpcUa:Telemetry:OtlpEndpoint</c> sets the OTLP endpoint. With no
|
||||
/// config the exporter stays Prometheus (the default).
|
||||
/// config the exporter stays Prometheus (the default). Since Telemetry 0.3.0 the same switch
|
||||
/// also sends the host's Serilog logs to that endpoint (scadaproj#55).
|
||||
/// </param>
|
||||
/// <param name="roles">
|
||||
/// The node's resolved roles (Program's single role authority). <see langword="null"/> resolves
|
||||
/// them from <paramref name="configuration"/>. They feed the Resource identity — see
|
||||
/// <see cref="ConfigureTelemetry"/>.
|
||||
/// </param>
|
||||
/// <returns>The service collection, for chaining.</returns>
|
||||
public static IServiceCollection AddOtOpcUaObservability(this IServiceCollection services, IConfiguration configuration)
|
||||
public static IServiceCollection AddOtOpcUaObservability(
|
||||
this IServiceCollection services,
|
||||
IConfiguration configuration,
|
||||
EffectiveRoles? roles = null)
|
||||
{
|
||||
return services.AddZbTelemetry(o =>
|
||||
{
|
||||
o.ServiceName = "otopcua";
|
||||
// Strict allowlists — see ObservedMeters / ObservedActivitySources.
|
||||
o.Meters = ObservedMeters;
|
||||
o.ActivitySources = ObservedActivitySources;
|
||||
if (Enum.TryParse<ZbExporter>(configuration["OtOpcUa:Telemetry:Exporter"], ignoreCase: true, out var exporter))
|
||||
o.Exporter = exporter;
|
||||
var otlp = configuration["OtOpcUa:Telemetry:OtlpEndpoint"];
|
||||
if (!string.IsNullOrWhiteSpace(otlp))
|
||||
o.OtlpEndpoint = otlp;
|
||||
});
|
||||
roles ??= EffectiveRoles.Resolve(configuration);
|
||||
return services.AddZbTelemetry(o => ConfigureTelemetry(o, configuration, roles));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates the shared telemetry options. Resource identity (scadaproj#55):
|
||||
/// <c>site.id</c> = the node's application <c>Cluster</c> id (the suffix of its
|
||||
/// <c>cluster-<ClusterId></c> role — one mesh per cluster, so it names the pair the node
|
||||
/// belongs to; omitted on a legacy node without one), <c>node.role</c> = its plain roles in
|
||||
/// canonical order (<c>admin</c>, <c>driver</c>, <c>admin,driver</c>, …; omitted when none).
|
||||
/// <c>service.version</c> defaults from the entry assembly's informational version (the git SHA).
|
||||
/// Operator keys under <c>OtOpcUa:Telemetry</c> are applied last (see
|
||||
/// <see cref="ZbTelemetryConfigurationExtensions.ApplyConfiguration"/>).
|
||||
/// </summary>
|
||||
/// <param name="o">The options to populate.</param>
|
||||
/// <param name="configuration">The host configuration (exporter keys).</param>
|
||||
/// <param name="roles">The node's resolved roles.</param>
|
||||
public static void ConfigureTelemetry(ZbTelemetryOptions o, IConfiguration configuration, EffectiveRoles roles)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(o);
|
||||
ArgumentNullException.ThrowIfNull(configuration);
|
||||
ArgumentNullException.ThrowIfNull(roles);
|
||||
|
||||
o.ServiceName = "otopcua";
|
||||
o.SiteId = roles.ClusterId;
|
||||
o.NodeRole = NodeRoleOf(roles);
|
||||
// Strict allowlists — see ObservedMeters / ObservedActivitySources.
|
||||
o.Meters = ObservedMeters;
|
||||
o.ActivitySources = ObservedActivitySources;
|
||||
// OtOpcUa:Telemetry:{Exporter, OtlpEndpoint, ServiceVersion, SiteId, NodeRole, ExportLogs,
|
||||
// ResourceAttributes:*} — an explicit SiteId/NodeRole there overrides the role-derived values.
|
||||
o.ApplyConfiguration(configuration.GetSection("OtOpcUa:Telemetry"));
|
||||
}
|
||||
|
||||
/// <summary>The plain (non-cluster) roles, lower-case, sorted and comma-joined; null when none.</summary>
|
||||
/// <param name="roles">The node's resolved roles.</param>
|
||||
/// <returns>For example <c>admin,driver</c>, or <see langword="null"/>.</returns>
|
||||
public static string? NodeRoleOf(EffectiveRoles roles)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(roles);
|
||||
var plain = roles.Gossiped
|
||||
.Where(r => !RoleParser.IsClusterRole(r))
|
||||
.Select(r => r.ToLowerInvariant())
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.Order(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
return plain.Length == 0 ? null : string.Join(",", plain);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -615,7 +615,7 @@ builder.Services.AddOtOpcUaSecrets(builder.Configuration);
|
||||
// probing it would throw when the readiness endpoint resolves the factory. hasDriver adds the
|
||||
// opcua-endpoint probe (ready + active): a driver node whose SDK failed to start is Unhealthy, not green.
|
||||
builder.Services.AddOtOpcUaHealth(hasAdmin, hasDriver);
|
||||
builder.Services.AddOtOpcUaObservability(builder.Configuration);
|
||||
builder.Services.AddOtOpcUaObservability(builder.Configuration, effectiveRoles);
|
||||
|
||||
// gRPC server plumbing shared by three endpoints: the LocalDb passive sync endpoint (driver-role,
|
||||
// mapped below on hasDriver && syncListenPort > 0), the ConfigServe artifact endpoint (admin-role,
|
||||
|
||||
Reference in New Issue
Block a user