feat(delmia-notifier): Program wiring, YES/NO reporter, diagnostics log

This commit is contained in:
Joseph Doherty
2026-06-26 05:18:19 -04:00
parent 71f680d542
commit 3931fa2101
4 changed files with 153 additions and 1 deletions
@@ -2,5 +2,72 @@ namespace ZB.MOM.WW.ScadaBridge.DelmiaNotifier;
internal static class Program
{
public static int Main(string[] args) => 0; // replaced in Task 7
public static async Task<int> Main(string[] args)
{
var stdout = Console.Out;
// Config first, so the diagnostic log can honour the configured LogPath even for early failures.
NotifierConfig cfg;
try
{
cfg = ConfigLoader.LoadFromDefaultFile();
}
catch (Exception ex)
{
new DiagLog(null).Write("config load failed: " + ex.Message);
return Reporter.Report(false, "config load failed: " + ex.Message, stdout);
}
var log = new DiagLog(cfg.ScadaBridge.LogPath);
try
{
var parse = ArgParser.Parse(args);
if (!parse.Ok)
{
log.Write("arg error: " + parse.Error);
return Reporter.Report(false, parse.Error!, stdout);
}
var baseUrls = ConfigLoader.SplitBaseUrls(cfg.ScadaBridge.BaseUrls);
if (baseUrls.Length == 0)
{
log.Write("no BaseUrls configured");
return Reporter.Report(false, "no BaseUrls configured", stdout);
}
var apiKey = ConfigLoader.ResolveApiKey(Environment.GetEnvironmentVariable);
if (apiKey is null)
{
log.Write("API key not configured (SCADABRIDGE_API_KEY)");
return Reporter.Report(false, "API key not configured (SCADABRIDGE_API_KEY)", stdout);
}
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(cfg.ScadaBridge.TimeoutSeconds) };
var sender = new LoggingRecipeSender(new HttpRecipeSender(http, apiKey), log);
log.Write($"notifying {baseUrls.Length} URL(s) for machine {parse.Payload!.MachineCode}");
var result = await Notifier.RunAsync(baseUrls, parse.Payload, sender, CancellationToken.None);
log.Write($"result: ok={result.Ok} reason={result.Reason}");
return Reporter.Report(result.Ok, result.Reason, stdout);
}
catch (Exception ex)
{
log.Write("unexpected error: " + ex);
return Reporter.Report(false, "unexpected error: " + ex.Message, stdout);
}
}
/// <summary>Decorates a sender to emit a per-attempt diagnostic line; keeps stdout reserved for the YES/NO contract.</summary>
private sealed class LoggingRecipeSender(IRecipeSender inner, DiagLog log) : IRecipeSender
{
public async Task<AttemptOutcome> SendAsync(string baseUrl, RecipeDownload payload, CancellationToken ct)
{
var outcome = await inner.SendAsync(baseUrl, payload, ct);
log.Write(outcome.Kind == AttemptKind.ConnectFailed
? $"attempt {baseUrl}: connect failed: {outcome.Error}"
: $"attempt {baseUrl}: HTTP {outcome.StatusCode}");
return outcome;
}
}
}