Add OpenTelemetry, performance metrics, and broaden logging + tests

Observability:
- New FuchsTelemetry (ActivitySource + Meter) defining business counters
  (invoices/reminders/reports rendered, emails/sms sent+failed, MT940 rows,
  MFR calls) and duration histograms (PDF render, report render, email send).
- Program.cs wires OpenTelemetry tracing (ASP.NET Core, HttpClient, SqlClient,
  app source) and metrics (ASP.NET Core, HttpClient, runtime, app meter).
  OTLP export is enabled only when Fuchs:Telemetry:OtlpEndpoint is set, so a
  missing collector never affects the app; disable via Fuchs:Telemetry:Enabled.

Instrumentation + logging:
- Services (Pdf, Invoice, Reminder, Report, Com, Banking, Widget, MfrFactory)
  now emit spans, record metrics, and log entry/result/timing/errors.
- Added dispatch + key-action logging to the previously silent handlers
  (Banking, Reminder, Reports, Requests).

Tests (137 total, +10):
- ProcessWebComServiceTests with a stub HttpMessageHandler cover success
  (API 200), failure (API 500, invalid email, empty mobile), disabled mode,
  the base64 attachment payload contract, and metric emission via MeterListener.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-05 14:02:13 +02:00
parent 8dee630abb
commit e04d590c3a
17 changed files with 473 additions and 16 deletions
@@ -15,12 +15,15 @@ public partial class IntranetController
{
private async Task<IActionResult> Do_Process_Bankings(string fn, string id, string code)
{
_logger.LogDebug("Do_Process_Bankings action={Action} user={User}", id, UserAccountID);
switch (id.ToLower())
{
case "auth":
return await JSONAsync(new { manage = 1 });
case "up":
_logger.LogInformation("Banking MT940 upload: {FileCount} file(s) user={User}",
Request.Form.Files.Count, UserAccountID);
foreach (var fle in Request.Form.Files)
{
using var stream = fle.OpenReadStream();
@@ -50,6 +53,8 @@ public partial class IntranetController
dtwa.OnCommandAfterError += (_, exc) =>
_intranet.debug_log("IntranetController.bam.up - command-after exception",
exc, UserAccountID, new { uid = dtwa.InstanceGUID, tmptbl });
_logger.LogDebug("Banking upload parsed {Rows} rows → temp table submit (user={User})",
tbl.Rows.Count, UserAccountID);
dtwa.DoSubmit();
}
return Ok();
@@ -17,6 +17,7 @@ public partial class IntranetController
{
private async Task<IActionResult> Do_Process_Reminder(string fn, string id, string code)
{
_logger.LogDebug("Do_Process_Reminder action={Action} user={User}", id, UserAccountID);
switch (id.ToLower())
{
case "get":
@@ -117,6 +118,8 @@ public partial class IntranetController
frdic.no("InvoiceFile", null!) is byte[] invFile)
remdoc[frdic.nz("InvoiceFileName")] = invFile;
_logger.LogInformation("Reminder conf: emailing finalized reminder {RemId} to {Email} user={User}",
remId, email.Trim(), UserAccountID);
bool sent = await _comService.SendEmailAsync(
$"inv_{remId}",
$"SanitärFuchs - {frdic.nz("subject").ne(frdic.nz("DocumentName"))}",
@@ -13,6 +13,7 @@ public partial class IntranetController
{
private async Task<IActionResult> Do_Process_Reports(string fn, string id, string code)
{
_logger.LogDebug("Do_Process_Reports action={Action} code={Code} user={User}", id, code, UserAccountID);
switch (id.ToLower())
{
case "auth":
@@ -19,6 +19,7 @@ public partial class IntranetController
{
private async Task<IActionResult> Do_Process_Requests(string fn, string id, string code)
{
_logger.LogDebug("Do_Process_Requests action={Action} user={User}", id, UserAccountID);
switch (id.ToLower())
{
case "auth":
@@ -277,6 +278,8 @@ public partial class IntranetController
double bal = Convert.ToDouble(frdic.no("InvoiceBalance", 0));
string terms = fdInv.PaymentTerms.Replace("wd", " Werktagen").Replace("d", " Tagen").Replace("wk", " Wochen").ne("10 Tagen");
string body = BuildInvoiceBody(bal, terms);
_logger.LogInformation("Request sconf: emailing finalized invoice {InvId} to {Email} user={User}",
invId, email.Trim(), UserAccountID);
bool sent = await _comService.SendEmailAsync(
$"inv_{invId}", $"Sanit\u00e4rFuchs - {frdic.nz("DocumentName")}",
body, email.Trim(), "", inv);