feat: Enhance error logging for PDF preview failures and SQL timeouts

- Implemented a new method `ShapeTableNames` to gather entity and navigation table names, ensuring no blanks or duplicates are included.
- Updated `FdsMfrClient` to handle cases where schema queries return no tables, logging appropriate warnings and preventing misleading errors.
- Introduced `PdfPreviewAsync` in `IntranetController` to manage PDF preview rendering, capturing detailed logs for failures and notifying users with readable error messages.
- Added comprehensive unit tests for error logging and table name shaping to ensure robustness and correctness.
- Documented decision to persist user action failures to the `fds__admin_logdebug` table, enhancing support diagnostics.
This commit is contained in:
Stefan
2026-09-16 16:35:04 +02:00
parent 38e0d55b09
commit fce83c528f
19 changed files with 609 additions and 56 deletions
+32 -3
View File
@@ -229,13 +229,42 @@ public class FuchsUserIdentity
public class FIS_SQLOptions : sqloptions
{
public FIS_SQLOptions(Dictionary<string, object>? context = null, ILogger? logger = null)
: this(context, logger, dbLog: (procedure, ex, data) => FuchsOcmsIntranet.Instance.debug_log(procedure, ex, data: data))
{
}
/// <summary>
/// Test seam: <paramref name="dbLog"/> receives what would otherwise go to
/// <see cref="Fuchs_intranet.debug_log"/> (code reference, exception, serialisable data).
/// </summary>
internal FIS_SQLOptions(Dictionary<string, object>? context, ILogger? logger,
Action<string, Exception?, object?> dbLog)
{
OnError = (procedure, ex, data) =>
{
logger?.LogError(ex, "SQL error in {Procedure}: {Message} — context={@Context}",
procedure, ex.Message, context);
try { FuchsOcmsIntranet.Instance.debug_log($"SQL Error in {procedure}", ex, data: context); }
// OCORE hands the failing statement in `data` (new { sql = ... }); `context` is the
// caller's fn/id/code. Both are needed to attribute a timeout in the DB log — logging
// only the (usually null) context left every "SQL Error in getSQLDataSet" row without
// the SQL that timed out.
var logData = BuildLogData(context, data);
logger?.LogError(ex, "SQL error in {Procedure}: {Message} — sql={@Sql} context={@Context}",
procedure, ex.Message, data, context);
try { dbLog($"SQL Error in {procedure}", ex, logData); }
catch { }
};
}
/// <summary>Merges the OCORE error payload (the SQL) with the caller context into one JSON-able object.</summary>
internal static Dictionary<string, object?> BuildLogData(IReadOnlyDictionary<string, object>? context, object? data)
{
var d = new Dictionary<string, object?>();
if (data != null)
{
// OCORE passes new { sql = "..." }; unwrap so the log column reads as the plain statement.
var sqlProp = data.GetType().GetProperty("sql");
d["sql"] = sqlProp?.GetValue(data) ?? data;
}
if (context is { Count: > 0 }) d["context"] = context;
return d;
}
}