feat(historian): drain/capacity/retention config knobs + startup config-warning validation
This commit is contained in:
@@ -15,6 +15,9 @@
|
||||
"Enabled": false,
|
||||
"DatabasePath": "alarm-historian.db",
|
||||
"PipeName": "OtOpcUaHistorian",
|
||||
"SharedSecret": ""
|
||||
"SharedSecret": "",
|
||||
"DrainIntervalSeconds": 5,
|
||||
"Capacity": 1000000,
|
||||
"DeadLetterRetentionDays": 30
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace ZB.MOM.WW.OtOpcUa.Runtime.Historian;
|
||||
|
||||
/// <summary>
|
||||
@@ -29,4 +32,28 @@ public sealed class AlarmHistorianOptions
|
||||
|
||||
/// <summary>Maximum number of queued rows the drain worker forwards in a single batch.</summary>
|
||||
public int BatchSize { get; init; } = 100;
|
||||
|
||||
/// <summary>Seconds between drain-worker ticks. Defaults to 5.</summary>
|
||||
public int DrainIntervalSeconds { get; init; } = 5;
|
||||
|
||||
/// <summary>Maximum queued rows before the sink evicts the oldest. Defaults to 1,000,000
|
||||
/// (matches <c>SqliteStoreAndForwardSink</c>'s <c>DefaultCapacity</c>).</summary>
|
||||
public long Capacity { get; init; } = 1_000_000;
|
||||
|
||||
/// <summary>Days to retain dead-lettered rows before purge. Defaults to 30.</summary>
|
||||
public int DeadLetterRetentionDays { get; init; } = 30;
|
||||
|
||||
/// <summary>Returns operator-facing misconfiguration warnings for an <c>Enabled</c> historian
|
||||
/// (empty when disabled or correctly configured). Pure — the registration logs each entry.</summary>
|
||||
/// <returns>Zero or more human-readable warning messages.</returns>
|
||||
public IReadOnlyList<string> Validate()
|
||||
{
|
||||
var warnings = new List<string>();
|
||||
if (!Enabled) return warnings;
|
||||
if (string.IsNullOrWhiteSpace(SharedSecret))
|
||||
warnings.Add("AlarmHistorian:SharedSecret is empty while the historian is enabled — the Wonderware sidecar Hello frame will carry an empty secret.");
|
||||
if (!Path.IsPathRooted(DatabasePath))
|
||||
warnings.Add($"AlarmHistorian:DatabasePath '{DatabasePath}' is relative — it resolves against the process working directory (e.g. System32 for a Windows service). Set an absolute path.");
|
||||
return warnings;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +72,9 @@ public static class ServiceCollectionExtensions
|
||||
var opts = configuration.GetSection(AlarmHistorianOptions.SectionName).Get<AlarmHistorianOptions>();
|
||||
if (opts is not { Enabled: true }) return services; // leave the Null default from AddOtOpcUaRuntime
|
||||
|
||||
foreach (var warning in opts.Validate())
|
||||
Serilog.Log.Logger.ForContext<SqliteStoreAndForwardSink>().Warning("{HistorianConfigWarning}", warning);
|
||||
|
||||
services.AddSingleton<IAlarmHistorianSink>(sp =>
|
||||
{
|
||||
// SqliteStoreAndForwardSink takes a Serilog ILogger (not Microsoft.Extensions.Logging).
|
||||
@@ -81,8 +84,10 @@ public static class ServiceCollectionExtensions
|
||||
opts.DatabasePath,
|
||||
writerFactory(opts, sp),
|
||||
Serilog.Log.Logger.ForContext<SqliteStoreAndForwardSink>(),
|
||||
batchSize: opts.BatchSize);
|
||||
sink.StartDrainLoop(TimeSpan.FromSeconds(5));
|
||||
batchSize: opts.BatchSize,
|
||||
capacity: opts.Capacity,
|
||||
deadLetterRetention: TimeSpan.FromDays(opts.DeadLetterRetentionDays));
|
||||
sink.StartDrainLoop(TimeSpan.FromSeconds(opts.DrainIntervalSeconds));
|
||||
return sink;
|
||||
});
|
||||
return services;
|
||||
|
||||
Reference in New Issue
Block a user